订单发送页：
<template>
  <div class="app-container">
    <!-- 1. 订单信息 -->
    <div class="order-info-section">
      <div class="section-header">
        <i class="el-icon-s-order"></i>
        订单信息
      </div>
      <el-form :model="sendForm" :inline="true" label-width="100px">
        <el-form-item label="选择订单：">
          <el-select 
            v-model="sendForm.orderId" 
            placeholder="请选择要发送的订单"
            style="width: 300px"
            @change="handleOrderSelect"
            filterable
          >
            <el-option
              v-for="order in availableOrders"
              :key="order.orderId"
              :label="`${order.orderCode} - ${order.customerNick}`"
              :value="order.orderId"
            />
          </el-select>
        </el-form-item>
        
        <el-form-item label="客户：" v-if="selectedOrder">
          <span class="readonly-value">{{ selectedOrder.customerNick }}</span>
        </el-form-item>
        
        <el-form-item label="合同编号：" v-if="selectedOrder">
          <span class="readonly-value">{{ selectedOrder.contractCode }}</span>
        </el-form-item>
        
        <el-form-item label="通知单名称：" v-if="selectedOrder">
          <span class="readonly-value">{{ noticeName }}</span>
        </el-form-item>
      </el-form>
    </div>

    <!-- 3. 库存数量核对 -->
    <div class="inventory-section" v-if="selectedOrder">
      <div class="section-header">
        <i class="el-icon-data-analysis"></i>
        库存数量核对
      </div>
      
      <div class="product-selector" style="margin-bottom: 20px;">
        <el-form :inline="true">
          <el-form-item label="请选择本单产品：">
            <el-select 
              v-model="selectedProductModel" 
              placeholder="选择本单产品"
              style="width: 200px;"
              @change="handleProductModelChange"
            >
              <el-option
                v-for="product in currentOrderProducts"
                :key="product.model"
                :label="`${product.model}`"
                :value="product.model"
              />
            </el-select>
          </el-form-item>
          
          <el-form-item v-if="selectedProductModel">
            <span class="demand-quantity" style="color: #e6a23c; font-weight: bold; margin-left: 10px;">
              需求数量: {{ getProductDemand(selectedProductModel) }}kg
            </span>
          </el-form-item>
          
          <el-form-item label="查看其他产品：" style="margin-left: 10px;">
            <el-select 
              v-model="otherProductModel" 
              placeholder="选择产品型号"
              filterable
              clearable
              style="width: 200px;"
              @change="handleOtherProductSelect"
            >
              <el-option
                v-for="dict in dict.type.product_models"
                :key="dict.value"
                :label="dict.label"
                :value="dict.value"
              />
            </el-select>
          </el-form-item>
        </el-form>
      </div>

      <!-- 库存汇总信息 -->
      <div v-if="selectedProductModel" class="inventory-summary">
        <el-row :gutter="20">
          <el-col :span="6">
            <div class="summary-item">
              <span class="label">匹配库存总数：</span>
              <span class="value">{{ totalInventoryQuantity }}kg</span>
            </div>
          </el-col>
          <el-col :span="6">
            <div class="summary-item">
              <span class="label">精确匹配总数：</span>
              <span class="value">{{ exactMatchQuantity }}kg</span>
            </div>
          </el-col>
          <el-col :span="6">
            <div class="summary-item">
              <span class="label">单后库存：</span>
              <span :class="exactInventoryStatusClass">
                {{ exactInventoryStatusText }}
              </span>
            </div>
          </el-col>
        </el-row>
      </div>
      
      <!-- 库存列表 -->
      <div v-if="selectedProductModel" class="inventory-list">
        <el-table 
          :data="filteredInventoryList" 
          v-loading="inventoryLoading"
          style="width: 98%; margin-top: 10px;"
          max-height="300"
          border
        >
          <el-table-column label="批次号" width="180" align="center" prop="batchCode">
            <template slot-scope="scope">
              <el-button type="text" @click="handleBatchView(scope.row)">
                {{ scope.row.batchCode }}
              </el-button>
            </template>
          </el-table-column>
          <el-table-column label="产品物料名称" width="180" align="center" prop="itemName" :show-overflow-tooltip="true"/>
          <el-table-column label="规格型号" align="center" prop="specification" :show-overflow-tooltip="true">
            <template slot-scope="scope">
              <div>
                {{ scope.row.specification }}
                <el-tag 
                  v-if="isExactMatch(scope.row.specification, selectedProductModel)" 
                  size="mini" 
                  type="success"
                  style="margin-left: 5px;"
                >
                  精确匹配
                </el-tag>
              </div>
            </template>
          </el-table-column>
          <el-table-column label="在库数量" align="center" prop="quantityOnhand" width="100">
            <template slot-scope="scope">
              <span :class="getQuantityClass(scope.row.quantityOnhand)">
                {{ scope.row.quantityOnhand }}
              </span>
            </template>
          </el-table-column>
          <el-table-column label="单位" align="center" prop="unitName" width="80"/>
          <el-table-column label="仓库" align="center" prop="warehouseName" width="120"/>
        </el-table>
      </div>
    </div>

    <!-- 4. 发送选项 -->
    <div class="send-options-section" v-if="selectedOrder">
      <div class="section-header">
        <i class="el-icon-s-promotion"></i>
        发送选项
      </div>
      
      <el-form :model="sendForm" label-width="100px">
        <!-- 第一行：发送目标、接收人、发货日期 -->
        <el-row :gutter="20">
          <el-col :span="8">
            <el-form-item label="发送目标：">
              <el-select 
                v-model="sendForm.sendType" 
                placeholder="选择发送目标"
                style="width: 100%"
              >
                <el-option
                  v-for="dict in dict.type.send_target"
                  :key="dict.value"
                  :label="dict.label"
                  :value="dict.value"
                />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item label="接收人：">
              <el-select 
                v-model="sendForm.receiverUserId" 
                placeholder="选择接收人"
                style="width: 100%"
                filterable
              >
                <el-option
                  v-for="user in receiverOptions"
                  :key="user.userId"
                  :label="user.nickName"
                  :value="user.userId"
                />
              </el-select>
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item label="发货日期：">
              <el-date-picker
                v-model="sendForm.shippingDate"
                type="date"
                placeholder="选择发货日期"
                value-format="yyyy-MM-dd"
                style="width: 100%"
                :picker-options="{
                  disabledDate(time) {
                    return time.getTime() < Date.now() - 8.64e7;
                  }
                }"
              />
            </el-form-item>
          </el-col>
        </el-row>

        <!-- 第二行：收货信息 -->
        <el-row :gutter="20">
          <el-col :span="24">
            <el-form-item label="收货信息：">
              <div class="address-radio-group">
                <el-radio-group v-model="sendForm.shippingAddress" @change="handleAddressSelect">
                  <!-- 第一行：地址1和地址2 -->
                  <div class="address-row" style="margin-bottom: 15px; display: flex;">
                    <!-- 地址1 -->
                    <div class="address-option" style="flex: 1; margin-right: 30px;">
                      <el-radio 
                        v-if="customerAddresses.length > 0" 
                        :label="customerAddresses[0].value" 
                        class="address-radio"
                      >
                        {{ customerAddresses[0].label }}
                      </el-radio>
                    </div>
                    
                    <!-- 地址2 -->
                    <div class="address-option" style="flex: 1;">
                      <el-radio 
                        v-if="customerAddresses.length > 1" 
                        :label="customerAddresses[1].value" 
                        class="address-radio"
                      >
                        {{ customerAddresses[1].label }}
                      </el-radio>
                    </div>
                  </div>
                  
                  <!-- 第二行：其他地址 -->
                  <div class="address-row" style="display: flex; align-items: center;">
                    <el-radio label="custom" class="address-radio">
                      其他地址
                    </el-radio>
                    <el-input
                      v-model="customAddress"
                      placeholder="请输入其他收货地址"
                      style="width: 400px; margin-left: 10px;"
                      size="small"
                      @input="handleCustomAddressInput"
                      @keydown.space.stop
                    />
                  </div>
                </el-radio-group>
              </div>
            </el-form-item>
          </el-col>
        </el-row>
    
        <!-- 提示和按钮 -->
        <div class="create-notice-tip">
          <el-button 
            type="primary" 
            icon="el-icon-document-add"
            @click="createSendRecord"
            :loading="createLoading"
            :disabled="!canCreateSendRecord"
            size="medium"
          >
            创建发货通知
          </el-button>
          <el-alert
            title="确认以上信息无误，即可创建发货通知"
            type="info"
            :closable="false"
            style="margin-bottom: 0; margin-left: 16px;"
          />
        </div>
      </el-form>
    </div>

    <!-- 5. 发货通知列表 -->
    <div class="send-records-section">
      <div class="section-header">
        <i class="el-icon-tickets"></i>
        发货通知列表
      </div>

      <!-- 搜索条件 -->
      <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="80px">
        <el-form-item label="通知单号" prop="noticeCode">
          <el-input
            v-model="queryParams.noticeCode"
            placeholder="请输入通知单编号"
            clearable
            style="width: 160px"
            @keyup.enter.native="handleQuery"
          />
        </el-form-item>
        <el-form-item label="订单编号" prop="orderCode">
          <el-input
            v-model="queryParams.orderCode"
            placeholder="请输入订单编号"
            clearable
            style="width: 160px"
            @keyup.enter.native="handleQuery"
          />
        </el-form-item>
        <el-form-item>
          <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
          <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
        </el-form-item>
      </el-form>

      <!-- 发送记录表格 -->
      <el-table v-loading="loading" :data="sendRecordList" style="width: 100%">
        <el-table-column label="通知单编号" align="center" prop="noticeCode" width="180" />
        <el-table-column label="产品信息" align="center" prop="productInfo" :show-overflow-tooltip="true" />
        <el-table-column label="防腐方案" align="center" prop="antiCorrosionPlan" width="120">
          <template slot-scope="scope">
            <dict-tag :options="dict.type.anti_corrosion_plan" :value="scope.row.antiCorrosionPlan"/>
          </template>
        </el-table-column>
        <el-table-column label="特殊说明" align="center" prop="productSpecialNote" :show-overflow-tooltip="true">
          <template slot-scope="scope">
            {{ getCombinedSpecialNotes(scope.row) }}
          </template>
        </el-table-column>
        <el-table-column label="发货日期" align="center" prop="shippingDate" width="120" />
        <el-table-column label="接收人" align="center" prop="receiverUserName" width="120">
          <template slot-scope="scope">
            {{ scope.row.receiverUserName || getReceiverName(scope.row.receiverUserId) }}
          </template>
        </el-table-column>
        <el-table-column label="收货信息" align="center" prop="shippingAddress" :show-overflow-tooltip="true" />
        <el-table-column label="发送状态" align="center" width="120">
          <template slot-scope="scope">
            <el-tag 
              :class="{
                'sent-btn': scope.row.sendStatus === 'SENT',
                'received-btn': scope.row.sendStatus === 'RECEIVED',
                'completed-btn': scope.row.sendStatus === 'COMPLETED'
              }"
              effect="dark"
            >
              <dict-tag :options="dict.type.sent_comment" :value="scope.row.sendStatus"/>
            </el-tag>
          </template>
        </el-table-column>
        <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="200">
          <template slot-scope="scope">
            <el-button
              size="mini"
              type="text"
              icon="el-icon-view"
              @click="handleViewRecord(scope.row)"
            >查看详情</el-button>
            <el-button
              size="mini"
              type="text"
              icon="el-icon-delete"
              @click="handleDeleteRecord(scope.row)"
              v-hasPermi="['mes:sales:ordersend:remove']"
            >删除</el-button>
          </template>
        </el-table-column>
      </el-table>

      <pagination
        v-show="total>0"
        :total="total"
        :page.sync="queryParams.pageNum"
        :limit.sync="queryParams.pageSize"
        @pagination="getList"
      />
    </div>

    <!-- 查看详情弹窗 -->
    <el-dialog title="发货通知详情" :visible.sync="viewRecordOpen" width="800px" append-to-body>
      <el-descriptions :column="2" border v-if="currentRecord">
        <el-descriptions-item label="通知单编号">{{ currentRecord.noticeCode }}</el-descriptions-item>
        <el-descriptions-item label="通知单名称">{{ currentRecord.noticeName }}</el-descriptions-item>
        <el-descriptions-item label="订单编号">{{ currentRecord.orderCode }}</el-descriptions-item>
        <el-descriptions-item label="合同编号">{{ currentRecord.contractCode }}</el-descriptions-item>
        <el-descriptions-item label="客户简称">{{ currentRecord.customerNick }}</el-descriptions-item>
        <el-descriptions-item label="发送人">{{ currentRecord.sender || currentRecord.createBy }}</el-descriptions-item>
        <el-descriptions-item label="发送类型">
          <dict-tag :options="dict.type.send_target" :value="currentRecord.sendType"/>
        </el-descriptions-item>
        <el-descriptions-item label="发送状态">
          <dict-tag :options="dict.type.sent_comment" :value="currentRecord.sendStatus"/>
        </el-descriptions-item>
        <el-descriptions-item label="发货日期">{{ currentRecord.shippingDate }}</el-descriptions-item>
        <el-descriptions-item label="发送时间">{{ currentRecord.sendTime }}</el-descriptions-item>
        <el-descriptions-item label="接收人"> {{ currentRecord.receiverUserName || getReceiverName(currentRecord.receiverUserId) }} </el-descriptions-item>
        <el-descriptions-item label="接收时间">{{ currentRecord.receiveTime || '未接收' }}</el-descriptions-item>
        <el-descriptions-item label="产品信息" :span="2">
          <div style="white-space: pre-line;">{{ currentRecord.productInfo }}</div>
        </el-descriptions-item>
        <el-descriptions-item label="防腐方案" :span="2">
          <dict-tag :options="dict.type.anti_corrosion_plan" :value="currentRecord.antiCorrosionPlan"/>
          <span v-if="currentRecord.antiCorrosionNote"> - {{ currentRecord.antiCorrosionNote }}</span>
        </el-descriptions-item>
        <el-descriptions-item label="产品特殊情况" :span="2">
          {{ currentRecord.productSpecialNote || '无' }}
        </el-descriptions-item>
        <el-descriptions-item label="包装要求" :span="2">
          {{ currentRecord.packageSpecialNote || '无' }}
        </el-descriptions-item>
        <el-descriptions-item label="其他说明" :span="2">
          {{ currentRecord.otherSpecialNote || '无' }}
        </el-descriptions-item>
        <el-descriptions-item label="收货信息" :span="2">
          <div style="white-space: pre-line;">{{ currentRecord.shippingAddress }}</div>
        </el-descriptions-item>
        <el-descriptions-item label="备注" :span="2">
          {{ currentRecord.remark || '无' }}
        </el-descriptions-item>
      </el-descriptions>
      
      <div slot="footer" class="dialog-footer">
        <el-button @click="viewRecordOpen = false">关闭</el-button>
      </div>
    </el-dialog>
  </div>
</template>

<script>
import { listOrder, getOrder } from "@/api/mes/sales/order";
import { getCustomer } from "@/api/mes/sales/customer";
import { listUser } from "@/api/system/user";
import { listWmstock } from "@/api/mes/wm/wmstock";
import { 
  listSendRecord, 
  getSendRecord, 
  createSendRecordFromOrder,
  delSendRecord
} from "@/api/mes/sales/ordersend";

export default {
  name: "OrderSend",
  dicts: ['send_target', 'sent_comment', 'anti_corrosion_plan', 'product_models'],
  data() {
    return {
      // 发送创建相关
      sendForm: {
        orderId: null,
        sendType: 'WAREHOUSE',
        receiverUserId: null,
        shippingDate: this.getDefaultShippingDate(),
        shippingAddress: '',
        remark: ''
      },
      selectedOrder: null,
      customerAddresses: [],
      receiverOptions: [],
      createLoading: false,
      customAddress: '', // 自定义地址

      // 库存相关
      selectedProductModel: null,
      otherProductModel: null,
      inventoryList: [],
      filteredInventoryList: [],
      inventoryLoading: false,

      // 发送记录列表相关
      loading: true,
      showSearch: true,
      total: 0,
      sendRecordList: [],
      queryParams: {
        pageNum: 1,
        pageSize: 10,
        noticeCode: null,
        orderCode: null
      },

      // 查看详情相关
      viewRecordOpen: false,
      currentRecord: null,

      // 订单列表
      availableOrders: []
    };
  },
  computed: {
    canCreateSendRecord() {
      // 检查是否有有效的收货地址
      let hasValidAddress = false;
      if (this.sendForm.shippingAddress === 'custom') {
        // 如果是其他地址，需要自定义地址有内容
        hasValidAddress = this.customAddress && this.customAddress.trim().length > 0;
      } else {
        // 如果是预设地址，直接检查 shippingAddress
        hasValidAddress = this.sendForm.shippingAddress && this.sendForm.shippingAddress.length > 0;
      }
      
      return (
        this.sendForm.orderId &&
        this.sendForm.sendType &&
        this.sendForm.receiverUserId &&
        this.sendForm.shippingDate &&
        hasValidAddress
      );
    },
    noticeName() {
      if (!this.selectedOrder || !this.selectedOrder.productItems) return '';
      const products = this.selectedOrder.productItems
        .filter(item => item.model)
        .map(item => item.model)
        .filter((value, index, self) => self.indexOf(value) === index);
      return products.length > 0 ? products.join('、') + '发货通知' : '发货通知';
    },
    currentOrderProducts() {
      if (!this.selectedOrder || !this.selectedOrder.productItems) return [];
      return this.selectedOrder.productItems.filter(item => 
        item.model && item.quantity
      ).map(item => ({
        model: item.model,
        quantity: parseFloat(item.quantity) || 0
      }));
    },
    totalInventoryQuantity() {
      return this.filteredInventoryList.reduce((total, item) => {
        return total + (parseFloat(item.quantityOnhand) || 0);
      }, 0);
    },
    exactMatchQuantity() {
      if (!this.selectedProductModel) return 0;
      return this.filteredInventoryList
        .filter(item => this.isExactMatch(item.specification, this.selectedProductModel))
        .reduce((total, item) => total + (parseFloat(item.quantityOnhand) || 0), 0);
    },
    exactInventoryStatusClass() {
      const demand = this.getProductDemand(this.selectedProductModel);
      return this.exactMatchQuantity >= demand ? 'value sufficient' : 'value insufficient';
    },
    exactInventoryStatusText() {
      const demand = this.getProductDemand(this.selectedProductModel);
      const diff = this.exactMatchQuantity - demand;
      if (diff >= 0) {
        return `充足 (余${diff}kg)`;
      } else {
        return `不足 (缺${-diff}kg)`;
      }
    }
  },
  created() {
    this.getAvailableOrders();
    this.getList();
    this.loadReceiverOptions();
  },
  methods: {
    /** 获取可用的订单列表 */
    getAvailableOrders() {
      listOrder({ pageSize: 1000 }).then(response => {
        this.availableOrders = response.rows;
      });
    },

    /** 查询发送记录列表 */
    getList() {
      this.loading = true;
      listSendRecord(this.queryParams).then(response => {
        this.sendRecordList = response.rows;
        this.total = response.total;
        this.loading = false;
      });
    },

    /** 订单选择变化 */
    handleOrderSelect(orderId) {
      if (orderId) {
        getOrder(orderId).then(response => {
          this.selectedOrder = response.data;
          // 加载客户地址
          if (this.selectedOrder.customerId) {
            this.loadCustomerAddresses(this.selectedOrder.customerId);
          }
          // 清空库存选择
          this.selectedProductModel = null;
          this.filteredInventoryList = [];
        });
      } else {
        this.selectedOrder = null;
        this.customerAddresses = [];
        this.sendForm.shippingAddress = '';
        this.customAddress = '';
      }
    },

    // 库存相关方法
    handleProductModelChange(model) {
      if (model) {
        this.selectedProductModel = model;
        this.searchInventoryByModel(model);
        this.otherProductModel = null;
      }
    },

    handleOtherProductSelect(model) {
      if (model) {
        this.otherProductModel = model;
        this.selectedProductModel = model; // 同步到主选择
        this.searchInventoryByModel(model);
      }
    },

    async searchInventoryByModel(model) {
      this.inventoryLoading = true;
      try {
        const baseModel = this.extractBaseModel(model);
        const searchKeywords = this.generateSearchKeywords(model, baseModel);
        
        let allInventory = [];
        
        for (const keyword of searchKeywords) {
          try {
            const response = await listWmstock({ 
              specification: keyword,
              pageSize: 500
            });
            
            if (response.rows?.length > 0) {
              const newItems = response.rows.filter(newItem => 
                !allInventory.some(existingItem => 
                  existingItem.materialStockId === newItem.materialStockId
                )
              );
              allInventory = [...allInventory, ...newItems];
            }
          } catch (error) {
            console.warn(`搜索关键词 "${keyword}" 失败:`, error);
          }
        }
        
        this.inventoryList = this.sortInventoryByRelevance(allInventory, model, baseModel);
        this.filteredInventoryList = this.inventoryList;
        
      } catch (error) {
        console.error('搜索库存失败:', error);
        this.$message.error('搜索库存失败: ' + (error.msg || '网络错误'));
        this.filteredInventoryList = [];
      } finally {
        this.inventoryLoading = false;
      }
    },

    isExactMatch(specification, searchModel) {
      if (!specification || !searchModel) return false;
      return specification === searchModel;
    },

    extractBaseModel(model) {
      if (!model) return '';
      const baseModel = model
        .replace(/\s*V\d+.*$/i, '')
        .replace(/\s*版本\d+.*$/i, '')
        .replace(/\s*Ver\.?\s*\d+.*$/i, '')
        .replace(/\s*第\d+[代版].*$/i, '')
        .replace(/\s*[\(（].*[\)）].*$/, '')
        .trim();
      return baseModel || model;
    },

    generateSearchKeywords(model, baseModel) {
      const keywords = new Set();
      keywords.add(model);
      if (baseModel && baseModel !== model) {
        keywords.add(baseModel);
      }
      if (baseModel) {
        keywords.add(`%${baseModel}%`);
      }
      const commonVersions = ['V3', 'V5', 'V6', 'V7', 'V8', '版本3', '版本5'];
      commonVersions.forEach(version => {
        if (baseModel) {
          keywords.add(`${baseModel} ${version}`);
        }
      });
      return Array.from(keywords);
    },

    sortInventoryByRelevance(inventoryList, searchModel, baseModel) {
      if (!inventoryList.length) return [];
      const scoredList = inventoryList.map(item => {
        let score = 0;
        const spec = item.specification || '';
        if (spec === searchModel) score += 100;
        if (spec.includes(searchModel)) score += 50;
        if (baseModel && spec.includes(baseModel)) score += 40;
        if (spec.startsWith(searchModel)) score += 30;
        if (baseModel && spec.startsWith(baseModel)) score += 20;
        const quantity = parseFloat(item.quantityOnhand) || 0;
        score += Math.min(quantity / 10, 15);
        return { ...item, score };
      });
      return scoredList
        .sort((a, b) => b.score - a.score)
        .map(item => {
          const { score, ...rest } = item;
          return rest;
        });
    },

    getProductDemand(model) {
      const product = this.currentOrderProducts.find(item => item.model === model);
      return product ? product.quantity : 0;
    },

    getQuantityClass(quantity) {
      const num = parseFloat(quantity) || 0;
      if (num > 100) return 'quantity-high';
      if (num > 50) return 'quantity-medium';
      return 'quantity-low';
    },

    /** 加载客户地址 */
    async loadCustomerAddresses(customerId) {
      try {
        const response = await getCustomer(customerId);
        const customer = response.data;
        
        this.customerAddresses = [];
        
        if (customer.shippingAddress1) {
          this.customerAddresses.push({
            value: customer.shippingAddress1,
            label: customer.shippingNote1 ? 
                   `地址1: ${customer.shippingAddress1} (${customer.shippingNote1})` : 
                   `地址1: ${customer.shippingAddress1}`
          });
        }
        
        if (customer.shippingAddress2) {
          this.customerAddresses.push({
            value: customer.shippingAddress2,
            label: customer.shippingNote2 ? 
                   `地址2: ${customer.shippingAddress2} (${customer.shippingNote2})` : 
                   `地址2: ${customer.shippingAddress2}`
          });
        }
        
        // 默认选择第一个地址
        if (this.customerAddresses.length > 0) {
          this.sendForm.shippingAddress = this.customerAddresses[0].value;
        }
        
      } catch (error) {
        console.error('加载客户地址失败:', error);
        this.customerAddresses = [];
      }
    },

    /** 处理自定义地址选择 */
    handleCustomAddressFocus() {
      this.sendForm.shippingAddress = '';
    },

    /** 地址选择变化 */
    handleAddressSelect(address) {
      if (address === 'custom') {
        // 选择了其他地址，清空预设地址选择
        this.sendForm.shippingAddress = 'custom';
      } else {
        // 选择了预设地址，清空自定义地址
        this.customAddress = '';
        this.sendForm.shippingAddress = address;
      }
    },
    
    /** 自定义地址输入时更新 */
    handleCustomAddressInput() {
      // 当用户输入自定义地址时，确保选中其他地址选项
      if (this.customAddress.trim() && this.sendForm.shippingAddress !== 'custom') {
        this.sendForm.shippingAddress = 'custom';
      }
    },

    /** 加载接收人选项 */
    loadReceiverOptions() {
      listUser({ pageSize: 1000 }).then(response => {
        this.receiverOptions = response.rows;
      });
    },
    
    /** 根据接收人ID获取接收人姓名 */
    getReceiverName(receiverUserId) {
      if (!receiverUserId) return '-';
      const receiver = this.receiverOptions.find(user => user.userId === receiverUserId);
      return receiver ? receiver.nickName : receiverUserId;
    },

    /** 创建发送记录 */
    createSendRecord() {
      // 处理收货地址
      let finalShippingAddress = this.sendForm.shippingAddress;
      if (this.sendForm.shippingAddress === 'custom' && this.customAddress) {
        finalShippingAddress = this.customAddress;
      }
    
      this.createLoading = true;
      
      // 构建产品明细数据
      const productDetails = this.selectedOrder.productItems ? this.selectedOrder.productItems.map(item => ({
        itemId: item.itemId,
        itemCode: item.itemCode,
        itemName: item.itemName,
        specification: item.specification || item.model, // 使用specification或model字段
        quantity: parseFloat(item.quantity) || 0,
        unitName: item.unitName || 'KG'
      })) : [];
    
      console.log('发送的产品明细:', productDetails);
    
      const sendData = {
        order: this.selectedOrder,
        sendType: this.sendForm.sendType,
        shippingDate: this.sendForm.shippingDate,
        shippingAddress: finalShippingAddress,
        remark: this.sendForm.remark,
        sender: this.$store.state.user.userName,
        receiverUserId: this.sendForm.receiverUserId,
        // 新增：传递产品明细
        productDetails: productDetails
      };
    
      createSendRecordFromOrder(sendData).then(response => {
        this.createLoading = false;
        this.$modal.msgSuccess("发货通知创建成功");
        // 重置表单
        this.sendForm = {
          orderId: null,
          sendType: 'WAREHOUSE',
          receiverUserId: null,
          shippingDate: this.getDefaultShippingDate(),
          shippingAddress: '',
          remark: ''
        };
        this.selectedOrder = null;
        this.customerAddresses = [];
        this.customAddress = '';
        this.selectedProductModel = null;
        this.filteredInventoryList = [];
        // 刷新列表
        this.getList();
        this.getAvailableOrders();
      }).catch(error => {
        this.createLoading = false;
        console.error('创建发货通知失败:', error);
        this.$modal.msgError("创建失败: " + (error.msg || '未知错误'));
      });
    },

    /** 合并特殊说明 */
    getCombinedSpecialNotes(record) {
      const notes = [];
      if (record.productSpecialNote) notes.push(`产品: ${record.productSpecialNote}`);
      if (record.packageSpecialNote) notes.push(`包装: ${record.packageSpecialNote}`);
      if (record.otherSpecialNote) notes.push(`其他: ${record.otherSpecialNote}`);
      return notes.length > 0 ? notes.join('; ') : '无';
    },

    /** 查看发送记录详情 */
    handleViewRecord(row) {
      getSendRecord(row.recordId).then(response => {
        this.currentRecord = response.data;
        this.viewRecordOpen = true;
      });
    },

    /** 删除发送记录 */
    handleDeleteRecord(row) {
      this.$modal.confirm('是否确认删除通知单编号为"' + row.noticeCode + '"的数据项？').then(() => {
        return delSendRecord(row.recordId);
      }).then(() => {
        this.getList();
        this.$modal.msgSuccess("删除成功");
      }).catch(() => {});
    },

    /** 获取默认发货日期（3天后） */
    getDefaultShippingDate() {
      const date = new Date();
      date.setDate(date.getDate() + 3);
      const year = date.getFullYear();
      const month = String(date.getMonth() + 1).padStart(2, '0');
      const day = String(date.getDate()).padStart(2, '0');
      return `${year}-${month}-${day}`;
    },

    handleQuery() {
      this.queryParams.pageNum = 1;
      this.getList();
    },

    resetQuery() {
      this.resetForm("queryForm");
      this.handleQuery();
    },

    handleBatchView(row) {
      console.log('查看批次:', row.batchCode);
    }
  }
};
</script>

<style scoped>
.section-header {
  display: flex;
  align-items: center;
  font-size: 16px;
  font-weight: bold;
  color: #303133;
  margin: 20px 0 16px 0;
  padding-bottom: 8px;
  border-bottom: 2px solid #409EFF;
}

.section-header i {
  margin-right: 8px;
  color: #409EFF;
}

.order-info-section,
.inventory-section,
.send-options-section,
.send-records-section {
  margin-bottom: 24px;
  padding: 20px;
  background: white;
  border-radius: 8px;
  border: 1px solid #e9ecef;
}

.readonly-value {
  color: #303133;
  font-weight: 500;
  padding: 0 8px;
}

.demand-quantity {
  color: #e6a23c;
  font-weight: bold;
}

.quantity-high {
  color: #67c23a;
  font-weight: bold;
}

.quantity-medium {
  color: #e6a23c;
  font-weight: bold;
}

.quantity-low {
  color: #f56c6c;
  font-weight: bold;
}

.summary-item {
  padding: 8px 12px;
  background: #f8f9fa;
  border-radius: 4px;
  text-align: center;
}

.summary-item .label {
  color: #606266;
  font-size: 13px;
}

.summary-item .value {
  font-weight: bold;
  margin-left: 5px;
}

.summary-item .value.sufficient {
  color: #67c23a;
}

.summary-item .value.insufficient {
  color: #f56c6c;
}

.create-notice-tip {
  display: flex;
  align-items: center;
  margin-top: 20px;
  padding-top: 16px;
  border-top: 1px solid #e4e7ed;
}

/* 地址选择样式 */
.address-radio-group {
  display: flex;
  flex-wrap: wrap;
  gap: 15px;
}

.address-radio {
  display: flex;
  align-items: center;
  margin-right: 20px;
}

:deep(.el-radio__label) {
  white-space: nowrap;
}

/* 地址选择样式 */
.address-radio-group {
  display: flex;
  flex-direction: column;
  gap: 15px;
}

.address-row {
  display: flex;
  align-items: center;
}

.address-radio {
  margin-right: 30px;
}

.other-address {
  white-space: nowrap;
}

:deep(.el-radio__label) {
  white-space: nowrap;
}

/* 地址选择样式 */
.address-option {
  display: flex;
  align-items: flex-start;
  flex: 1;
}

.address-radio {
  white-space: nowrap;
  margin-right: 10px;
  height: 32px;
  line-height: 32px;
}

.address-content {
  flex: 1;
  min-height: 32px;
  line-height: 32px;
  color: #606266;
}

:deep(.el-radio) {
  align-items: flex-start;
}

:deep(.el-radio__label) {
  padding-left: 8px;
}

/* 发送状态颜色 */
.sent-btn {
  background-color: #e6a23c !important;
  border-color: #e6a23c !important;
  color: white !important;
}

.received-btn {
  background-color: #409eff !important;
  border-color: #409eff !important;
  color: white !important;
}

.completed-btn {
  background-color: #67c23a !important;
  border-color: #67c23a !important;
  color: white !important;
}
</style>

发货通知单页：
<template>
  <div class="app-container">
    <!-- 搜索条件 -->
    <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="100px">
      <el-form-item label="合同编号" prop="contractCode">
        <el-input
          v-model="queryParams.contractCode"
          placeholder="请输入合同编号"
          clearable
          style="width: 160px"
          @keyup.enter.native="handleQuery"
        />
      </el-form-item>
      <el-form-item label="产品批号" prop="productBatch">
        <el-input
          v-model="queryParams.productBatch"
          placeholder="请输入产品批号"
          clearable
          style="width: 160px"
          @keyup.enter.native="handleQuery"
        />
      </el-form-item>
      <el-form-item label="客户简称" prop="customerNick">
        <el-input
          v-model="queryParams.customerNick"
          placeholder="请输入客户简称"
          clearable
          style="width: 160px"
          @keyup.enter.native="handleQuery"
        />
      </el-form-item>
      <el-form-item label="发货日期" prop="shippingDateRange">
        <el-date-picker
          v-model="queryParams.shippingDateRange"
          type="daterange"
          range-separator="至"
          start-placeholder="开始日期"
          end-placeholder="结束日期"
          value-format="yyyy-MM-dd"
          style="width: 220px"
        />
      </el-form-item>
      <el-form-item>
        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
      </el-form-item>
    </el-form>

    <!-- 发货通知单列表 -->
    <el-table v-loading="loading" :data="sendRecordList" style="width: 100%">
      <el-table-column label="通知单编号" align="center" prop="noticeCode" width="180" />
      <el-table-column label="通知单名称" align="center" prop="noticeName" width="200" :show-overflow-tooltip="true" />
      <el-table-column label="客户简称" align="center" prop="customerNick" width="120" />
      <el-table-column label="产品信息" align="center" prop="productInfo" :show-overflow-tooltip="true" />
      <el-table-column label="防腐方案" align="center" prop="antiCorrosionPlan" width="135">
        <template slot-scope="scope">
          <dict-tag :options="dict.type.anti_corrosion_plan" :value="scope.row.antiCorrosionPlan"/>
        </template>
      </el-table-column>
      <el-table-column label="特殊说明" align="center" prop="productSpecialNote" :show-overflow-tooltip="true" />
      <el-table-column label="发货日期" align="center" prop="shippingDate" width="120" />
      <el-table-column label="收货信息" align="center" prop="shippingAddress" :show-overflow-tooltip="true" />
      <!-- 发送人列 -->
        <el-table-column label="发送人" align="center" width="120">
          <template slot-scope="scope">
            {{ scope.row.sender || scope.row.createBy || '-' }}
          </template>
        </el-table-column>
        <el-table-column label="通知状态" align="center" width="120">
          <template slot-scope="scope">
            <el-tag 
              :class="{
                'sent-btn': scope.row.sendStatus === 'SENT',
                'received-btn': scope.row.sendStatus === 'RECEIVED', 
                'completed-btn': scope.row.sendStatus === 'COMPLETED'
              }"
              effect="dark"
            >
              <dict-tag :options="dict.type.sent_comment" :value="scope.row.sendStatus"/>
            </el-tag>
          </template>
        </el-table-column>
      <el-table-column label="操作" align="center" class-name="small-padding fixed-width" width="280">
        <template slot-scope="scope">
          <el-button
            size="mini"
            type="text"
            icon="el-icon-view"
            @click="handleView(scope.row)"
          >查看详情</el-button>
          <el-button
            size="mini"
            type="text"
            icon="el-icon-check"
            @click="handleConfirmReceive(scope.row)"
            v-if="scope.row.sendStatus === 'SENT'"
          >确认接收</el-button>
          <el-button
            size="mini"
            type="text"
            icon="el-icon-truck"
            @click="handleGoShipping(scope.row)"
            v-if="scope.row.sendStatus === 'RECEIVED'"
          >前往发货</el-button>
        </template>
      </el-table-column>
    </el-table>

    <pagination
      v-show="total>0"
      :total="total"
      :page.sync="queryParams.pageNum"
      :limit.sync="queryParams.pageSize"
      @pagination="getList"
    />

    <!-- 查看详情弹窗 -->
    <el-dialog title="发货通知详情" :visible.sync="viewOpen" width="800px" append-to-body>
      <el-descriptions :column="2" border v-if="currentRecord">
        <el-descriptions-item label="通知单编号">{{ currentRecord.noticeCode }}</el-descriptions-item>
        <el-descriptions-item label="通知单名称">{{ currentRecord.noticeName }}</el-descriptions-item>
        <el-descriptions-item label="订单编号">{{ currentRecord.orderCode }}</el-descriptions-item>
        <el-descriptions-item label="合同编号">{{ currentRecord.contractCode }}</el-descriptions-item>
        <el-descriptions-item label="客户简称">{{ currentRecord.customerNick }}</el-descriptions-item>
        <el-descriptions-item label="发送类型">
          <dict-tag :options="dict.type.send_target" :value="currentRecord.sendType"/>
        </el-descriptions-item>
        <el-descriptions-item label="通知状态">
          <dict-tag :options="dict.type.sent_comment" :value="currentRecord.sendStatus"/>
        </el-descriptions-item>
        <el-descriptions-item label="发货日期">{{ currentRecord.shippingDate }}</el-descriptions-item>
        <el-descriptions-item label="发送时间">{{ currentRecord.sendTime }}</el-descriptions-item>
        <el-descriptions-item label="发送人">{{ getSenderName(currentRecord) }}</el-descriptions-item>
        <el-descriptions-item label="接收人">
          {{ currentRecord.receiverUserName || getReceiverName(currentRecord.receiverUserId) }}
        </el-descriptions-item>
        <el-descriptions-item label="接收时间">{{ currentRecord.receiveTime || '未接收' }}</el-descriptions-item>
        <el-descriptions-item label="产品信息" :span="2">
          <div style="white-space: pre-line;">{{ currentRecord.productInfo }}</div>
        </el-descriptions-item>
        <el-descriptions-item label="防腐方案" :span="2">
          <dict-tag :options="dict.type.anti_corrosion_plan" :value="currentRecord.antiCorrosionPlan"/>
          <span v-if="currentRecord.antiCorrosionNote"> - {{ currentRecord.antiCorrosionNote }}</span>
        </el-descriptions-item>
        <el-descriptions-item label="产品特殊情况" :span="2">
          {{ currentRecord.productSpecialNote || '无' }}
        </el-descriptions-item>
        <el-descriptions-item label="包装要求" :span="2">
          {{ currentRecord.packageSpecialNote || '无' }}
        </el-descriptions-item>
        <el-descriptions-item label="其他说明" :span="2">
          {{ currentRecord.otherSpecialNote || '无' }}
        </el-descriptions-item>
        <el-descriptions-item label="收货信息" :span="2">
          <div style="white-space: pre-line;">{{ currentRecord.shippingAddress }}</div>
        </el-descriptions-item>
        <el-descriptions-item label="备注" :span="2">
          {{ currentRecord.remark || '无' }}
        </el-descriptions-item>
      </el-descriptions>
      
      <div slot="footer" class="dialog-footer">
        <el-button @click="viewOpen = false">关闭</el-button>
      </div>
    </el-dialog>
  </div>
</template>

<script>
import { listSendRecord, getSendRecord, confirmReceive } from "@/api/mes/wm/salesnotice";
import { listUser } from "@/api/system/user";
export default {
  name: "WarehouseNotice",
  dicts: ['send_target', 'sent_comment', 'anti_corrosion_plan'],
  data() {
    return {
      // 遮罩层
      loading: true,
      // 显示搜索条件
      showSearch: true,
      // 总条数
      total: 0,
      // 发货通知单表格数据
      sendRecordList: [],
      // 查看详情弹窗
      viewOpen: false,
      currentRecord: null,
      // 查询参数
      queryParams: {
        pageNum: 1,
        pageSize: 10,
        contractCode: null,
        productBatch: null,
        customerNick: null,
        shippingDateRange: []
      },
      userList: [], // 用户列表
      userLoading: false
    };
  },
  created() {
    this.getList();
    this.loadUserList(); // 加载用户列表
  },
  methods: {
    /** 查询发货通知单列表 */
    getList() {
      this.loading = true;
      listSendRecord(this.queryParams).then(response => {
        this.sendRecordList = response.rows;
        this.total = response.total;
        this.loading = false;
      });
    },

    /** 加载用户列表 */
    loadUserList() {
      this.userLoading = true;
      // 调用用户列表接口
      listUser({ pageSize: 1000 }).then(response => {
        this.userList = response.rows;
        this.userLoading = false;
      }).catch(error => {
        this.userLoading = false;
        console.error('加载用户列表失败:', error);
      });
    },
    
    /** 根据用户ID获取用户名 */
    getReceiverName(receiverUserId) {
      if (!receiverUserId) return '-';
      const user = this.userList.find(u => u.userId === receiverUserId);
      return user ? user.nickName : receiverUserId;
    },

    // 获取发送人姓名
    getSenderName(record) {
      return record.sender || record.createBy || '-';
    },

    /** 搜索按钮操作 */
    handleQuery() {
      this.queryParams.pageNum = 1;
      this.getList();
    },
    /** 重置按钮操作 */
    resetQuery() {
      this.resetForm("queryForm");
      this.handleQuery();
    },
    /** 查看详情 */
    handleView(row) {
      getSendRecord(row.recordId).then(response => {
        this.currentRecord = response.data;
        
        // 如果没有 receiverUserName，根据 receiverUserId 设置
        if (!this.currentRecord.receiverUserName && this.currentRecord.receiverUserId) {
          this.currentRecord.receiverUserName = this.getReceiverName(this.currentRecord.receiverUserId);
        }
        
        this.viewOpen = true;
      });
    },
    /** 确认接收 */
    handleConfirmReceive(row) {
      this.$modal.confirm('确认接收发货通知单 "' + row.noticeCode + '" 吗？').then(() => {
        confirmReceive(row.recordId).then(response => {
          this.$modal.msgSuccess("接收成功");
          this.getList();
        });
      });
    },
    /** 前往发货 */
    handleGoShipping(row) {
      this.$message.info('发货功能待开发');
      // TODO: 跳转到发货操作页面
    }
  }
};
</script>

<style scoped>
.app-container {
  padding: 20px;
}

/* 发送状态颜色 */
.sent-btn {
  background-color: #e6a23c !important;
  border-color: #e6a23c !important;
  color: white !important;
}

.received-btn {
  background-color: #409eff !important;
  border-color: #409eff !important;
  color: white !important;
}

.completed-btn {
  background-color: #67c23a !important;
  border-color: #67c23a !important;
  color: white !important;
}
</style>

销售出库页：
<template>
  <div class="app-container">
    <el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="100px">
      <el-form-item label="出库单号" prop="salesCode">
        <el-input
          v-model="queryParams.salesCode"
          placeholder="请输入出库单号"
          clearable
          style="width: 180px"
          @keyup.enter.native="handleQuery"
        />
      </el-form-item>
      <el-form-item label="销售订单号" prop="soCode">
        <el-input
          v-model="queryParams.soCode"
          placeholder="请输入销售订单号"
          clearable
          style="width: 180px"
          @keyup.enter.native="handleQuery"
        />
      </el-form-item>
      <el-form-item label="客户简称" prop="clientNick">
        <el-input
          v-model="queryParams.clientNick"
          placeholder="请输入客户简称"
          clearable
          style="width: 180px"
          @keyup.enter.native="handleQuery"
        />
      </el-form-item>
      <el-form-item label="产品型号" prop="productModel">
        <el-input
          v-model="queryParams.productModel"
          placeholder="请输入产品型号"
          clearable
          style="width: 180px"
          @keyup.enter.native="handleQuery"
        />
      </el-form-item>
      <el-form-item>
        <el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
        <el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
      </el-form-item>
    </el-form>

    <el-row :gutter="10" class="mb8">
      <el-col :span="1.5">
        <el-button
          type="primary"
          plain
          icon="el-icon-plus"
          size="mini"
          @click="handleAdd"
          v-hasPermi="['mes:wm:productsales:add']"
        >新增</el-button>
      </el-col>
      <right-toolbar :showSearch.sync="showSearch" @queryTable="getSalesList"></right-toolbar>
    </el-row>

    <el-table v-loading="loading" :data="productsalesList" @selection-change="handleSelectionChange">
      <el-table-column type="selection" width="55" align="center" />
      <el-table-column label="出库单号" width="220px" align="center" prop="salesCode" />
      <el-table-column label="销售订单号" width="180px" align="center" prop="soCode" />
      <el-table-column label="客户简称" width="150px" align="center" prop="clientNick" />
      <el-table-column label="出库内容" align="center" prop="outboundContent" :show-overflow-tooltip="true">
        <template slot-scope="scope">
          <div class="outbound-content">
            {{ scope.row.outboundContent }}
          </div>
        </template>
      </el-table-column>
      <el-table-column label="出库日期" align="center" prop="salesDate" width="120">
        <template slot-scope="scope">
          <span>{{ parseTime(scope.row.salesDate, '{y}-{m}-{d}') }}</span>
        </template>
      </el-table-column>
      <el-table-column label="出库人" align="center" prop="createBy" width="120" />
      <el-table-column label="操作" width="250px" align="center" class-name="small-padding fixed-width">
        <template slot-scope="scope">
          <el-button
            size="mini"
            type="text"
            icon="el-icon-view"
            @click="handleView(scope.row)"
            v-hasPermi="['mes:wm:productsales:query']"
          >查看详情</el-button>
          <el-button
            size="mini"
            type="text"
            icon="el-icon-edit"
            @click="handleUpdate(scope.row)"
            v-if="scope.row.status == 'PREPARE'"
            v-hasPermi="['mes:wm:productsales:edit']"
          >修改</el-button>
          <el-button
            size="mini"
            type="text"
            icon="el-icon-delete"
            @click="handleDelete(scope.row)"
            v-if="scope.row.status == 'PREPARE' || scope.row.status == 'UNEXECUTE'"
            v-hasPermi="['mes:wm:productsales:remove']"
          >删除</el-button>
          <el-button
            size="mini"
            type="text"
            icon="el-icon-video-play"
            @click="handleExecute(scope.row)"
            v-if="scope.row.status == 'PREPARE' || scope.row.status == 'UNEXECUTE'"
            v-hasPermi="['mes:wm:productsales:edit']"
          >执行出库</el-button>
        </template>
      </el-table-column>
    </el-table>

    <pagination
      v-show="total>0"
      :total="total"
      :page.sync="queryParams.pageNum"
      :limit.sync="queryParams.pageSize"
      @pagination="getSalesList"
    />

    <!-- 添加或修改销售出库单对话框 -->
    <el-dialog :title="title" :visible.sync="open" width="1400px" append-to-body>
      <el-form ref="form" :model="form" :rules="rules" label-width="120px">
        <el-row>
          <el-col :span="12">
            <el-form-item label="销售订单号" prop="soCode">
                <el-select 
                  v-model="form.soCode" 
                  placeholder="请选择已接收的销售订单"
                  style="width: 300px"
                  @change="handleOrderSelect"
                  filterable
                  clearable
                  :loading="orderLoading"
                >
                  <el-option
                    v-for="order in receivedOrderList"
                    :key="order.orderCode"
                    :label="`${order.orderCode} - ${order.customerNick}`"
                    :value="order.orderCode"
                  />
                </el-select>
              <div class="order-tip" v-if="receivedOrderList.length === 0 && !orderLoading">
                暂无已接收的发货通知单
              </div>
            </el-form-item>
          </el-col>
          <el-col :span="12">
            <el-form-item label="出库单号" prop="salesCode">
              <el-input v-model="form.salesCode" placeholder="自动生成或手动输入" style="width: 250px" />
              <el-switch 
                v-model="autoGenFlag"
                active-text="自动生成"
                inactive-text="手动输入"
                @change="handleAutoGenChange"
                style="margin-left: 10px;"
                v-if="optType != 'view' && form.status =='PREPARE'"
              />
            </el-form-item>
          </el-col>
        </el-row>

        <!-- 出库单名称 -->
        <el-row>
          <el-col :span="24">
            <el-form-item label="出库单名称">
              <el-input v-model="form.salesName" readonly style="width: 600px">
                <template slot="prepend">自动生成</template>
              </el-input>
            </el-form-item>
          </el-col>
        </el-row>

        <!-- 订单信息展示区域 -->
        <el-card v-if="form.soCode" class="order-info-card" shadow="never">
          <div slot="header" class="clearfix">
            <span class="order-info-title">订单信息（来自发货通知单）</span>
          </div>
          <el-row :gutter="20">
            <el-col :span="8">
              <div class="info-item">
                <label>客户简称：</label>
                <span class="info-value">{{ form.clientNick }}</span>
              </div>
            </el-col>
            <el-col :span="16">
              <div class="info-item">
                <label>产品信息：</label>
                <span class="info-value product-info-highlight">{{ form.productInfo }}</span>
              </div>
            </el-col>
          </el-row>
          <el-row :gutter="20">
            <el-col :span="8">
              <div class="info-item">
                <label>防腐方案：</label>
                <span class="info-value anti-corrosion-highlight">
                  <dict-tag :options="dict.type.anti_corrosion_plan" :value="form.antiCorrosionPlan"/>
                </span>
              </div>
            </el-col>
            <el-col :span="16">
              <div class="info-item">
                <label>收货信息：</label>
                <span class="info-value">{{ form.receiverInfo }}</span>
              </div>
            </el-col>
          </el-row>
          
          <!-- 三个特殊要求 -->
          <el-row :gutter="20" v-if="form.productSpecialNote">
            <el-col :span="24">
              <div class="info-item">
                <label>产品特殊要求：</label>
                <span class="info-value special-requirement">{{ form.productSpecialNote }}</span>
              </div>
            </el-col>
          </el-row>
          <el-row :gutter="20" v-if="form.packageSpecialNote">
            <el-col :span="24">
              <div class="info-item">
                <label>包装特殊要求：</label>
                <span class="info-value special-requirement">{{ form.packageSpecialNote }}</span>
              </div>
            </el-col>
          </el-row>
          <el-row :gutter="20" v-if="form.otherSpecialNote">
            <el-col :span="24">
              <div class="info-item">
                <label>其他特殊要求：</label>
                <span class="info-value special-requirement">{{ form.otherSpecialNote }}</span>
              </div>
            </el-col>
          </el-row>
          
          <!-- 发货通知信息 -->
          <el-row :gutter="20" v-if="currentSendRecord">
            <el-col :span="8">
              <div class="info-item">
                <label>通知单号：</label>
                <span class="info-value">{{ currentSendRecord.noticeCode }}</span>
              </div>
            </el-col>
            <el-col :span="8">
              <div class="info-item">
                <label>计划发货日期：</label>
                <span class="info-value">{{ currentSendRecord.shippingDate }}</span>
              </div>
            </el-col>
            <el-col :span="8">
              <div class="info-item">
                <label>发送人：</label>
                <span class="info-value">{{ currentSendRecord.sender || currentSendRecord.createBy }}</span>
              </div>
            </el-col>
          </el-row>
        </el-card>

        <!-- 出库日期和出库人并排 -->
        <el-row>
          <el-col :span="8">
            <el-form-item label="出库日期" prop="salesDate">
              <el-date-picker clearable
                v-model="form.salesDate"
                type="date"
                value-format="yyyy-MM-dd"
                placeholder="请选择出库日期"
                style="width: 220px"
              />
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item label="出库人" prop="createBy">
              <el-input v-model="form.createBy" placeholder="请输入出库人" style="width: 220px">
                <el-button slot="append" icon="el-icon-user" @click="handleSelectOperator"></el-button>
              </el-input>
            </el-form-item>
          </el-col>
          <el-col :span="8">
            <el-form-item label=" ">
              <el-button 
                type="primary" 
                @click="saveBasicInfo" 
                v-if="optType !== 'view' && form.status =='PREPARE'"
                style="width: 120px;"
                :loading="basicInfoLoading"
              >
                保存基本信息
              </el-button>
              <span class="save-tip" v-if="form.salesId" style="margin-left: 10px; color: #67c23a;">
                ✓ 请添加出库物料
              </span>
            </el-form-item>
          </el-col>
        </el-row>
      </el-form>
      
      <!-- 物料信息部分 -->
      <el-divider content-position="center">出库物料信息</el-divider>
      <el-card shadow="always" class="box-card">
        <!-- 物料操作按钮 -->
        <div class="material-operations" v-if="optType !== 'view'">
          <el-button
            type="primary"
            plain
            icon="el-icon-plus"
            size="mini"
            @click="handleAddMaterial"
            :disabled="!form.salesId"
          >添加出库产品</el-button>
          <span class="material-tip" style="margin-left: 10px; color: #909399; font-size: 13px;">
            {{ form.salesId ? '请添加需要出库的物料批次' : '请先保存基本信息' }}
          </span>
        </div>

        <!-- 物料添加表单 -->
        <div v-if="showMaterialForm" class="material-form-section">
          <el-divider content-position="left">添加出库物料</el-divider>
          
          <!-- 第一步：选择产品 -->
          <div v-if="currentStep === 1" class="step-content">
            <el-form ref="productForm" :model="productForm" :rules="productRules" label-width="100px">
              <el-row :gutter="20">
                <el-col :span="8">
                  <el-form-item label="物料编码" prop="itemCode">
                    <el-input 
                      v-model="productForm.itemCode" 
                      placeholder="请选择物料编码" 
                      readonly
                    >
                      <el-button 
                        slot="append" 
                        icon="el-icon-search" 
                        @click="handleProductSelect"
                      ></el-button>
                    </el-input>
                  </el-form-item>
                </el-col>
                <el-col :span="8">
                  <el-form-item label="产品名称">
                    <el-input v-model="productForm.itemName" readonly />
                  </el-form-item>
                </el-col>
                <el-col :span="8">
                  <el-form-item label="规格型号">
                    <el-input v-model="productForm.specification" readonly />
                  </el-form-item>
                </el-col>
              </el-row>
            </el-form>

            <div v-if="productForm.itemName" class="batch-selection-section">
              <div class="section-title">请选择出库批次：</div>
              
              <!-- 库存搜索 -->
              <div class="inventory-search" style="margin-bottom: 15px;">
                <el-input
                  v-model="inventorySearchKeyword"
                  placeholder="搜索产品型号、批次号、库位..."
                  style="width: 300px"
                  clearable
                  @input="handleInventorySearch"
                >
                  <el-button slot="append" icon="el-icon-search" @click="searchInventory"></el-button>
                </el-input>
              </div>

              <!-- 库存列表 -->
              <el-table 
                :data="filteredInventoryList" 
                v-loading="inventoryLoading"
                style="width: 100%; margin-top: 10px;"
                max-height="300"
                border
                @selection-change="handleBatchSelectionChange"
              >
                <el-table-column type="selection" width="55" align="center" />
                <el-table-column label="产品编码" width="120" align="center" prop="itemCode" />
                <el-table-column label="产品名称" width="150" align="center" prop="itemName" :show-overflow-tooltip="true"/>
                <el-table-column label="规格型号" align="center" prop="specification" :show-overflow-tooltip="true"/>
                <el-table-column label="防腐方案" width="120" align="center" prop="antiCorrosionPlan">
                  <template slot-scope="scope">
                    {{ scope.row.antiCorrosionPlan || '--' }}
                  </template>
                </el-table-column>
                <el-table-column label="在库数量" align="center" prop="quantityOnhand" width="100">
                  <template slot-scope="scope">
                    <span :class="getQuantityClass(scope.row.quantityOnhand)">
                      {{ scope.row.quantityOnhand }}
                    </span>
                  </template>
                </el-table-column>
                <el-table-column label="单位" align="center" prop="unitName" width="80"/>
                <el-table-column label="批次号" width="120" align="center" prop="batchCode" />
                <el-table-column label="库位" width="180" align="center" prop="locationInfo">
                  <template slot-scope="scope">
                    {{ scope.row.warehouseName }}/{{ scope.row.locationName }}/{{ scope.row.areaName }}
                  </template>
                </el-table-column>
              </el-table>

              <div class="step-actions" style="margin-top: 20px; text-align: center;">
                <el-button type="primary" @click="goToStep2" :disabled="selectedBatches.length === 0">
                  下一步：填写出库数量
                </el-button>
                <el-button @click="cancelMaterialAdd">取消</el-button>
              </div>
            </div>
          </div>

          <!-- 第二步：填写出库数量 -->
          <div v-if="currentStep === 2" class="step-content">
            <div class="section-title">填写出库数量：</div>
            
            <el-table :data="batchFormList" style="width: 100%" border>
              <el-table-column label="批次号" width="120" align="center" prop="batchCode" />
              <el-table-column label="产品名称" width="150" align="center" prop="itemName" />
              <el-table-column label="规格型号" align="center" prop="specification" :show-overflow-tooltip="true"/>
              <el-table-column label="在库数量" align="center" width="100" prop="quantityOnhand">
                <template slot-scope="scope">
                  {{ scope.row.quantityOnhand }} {{ scope.row.unitName }}
                </template>
              </el-table-column>
              <el-table-column label="出库数量" align="center" width="150">
                <template slot-scope="scope">
                  <el-input-number
                    v-model="scope.row.quantitySales"
                    :min="0"
                    :max="scope.row.quantityOnhand"
                    :precision="3"
                    controls-position="right"
                    style="width: 120px"
                    @change="validateQuantity(scope.row)"
                  />
                </template>
              </el-table-column>
              <el-table-column label="库位" width="180" align="center">
                <template slot-scope="scope">
                  {{ scope.row.warehouseName }}/{{ scope.row.locationName }}/{{ scope.row.areaName }}
                </template>
              </el-table-column>
              
              <el-table-column label="备注" align="center">
                <template slot-scope="scope">
                  <el-input
                    v-model="scope.row.remark"
                    placeholder="请输入备注"
                    size="small"
                  />
                </template>
              </el-table-column>
            </el-table>

            <div class="step-actions" style="margin-top: 20px; text-align: center;">
              <el-button @click="currentStep = 1">上一步</el-button>
              <el-button type="primary" @click="submitBatchForm">确 定</el-button>
              <el-button @click="cancelMaterialAdd">取消</el-button>
            </div>
          </div>
        </div>

        <!-- 物料列表 -->
        <el-table 
          v-loading="materialLoading" 
          :data="productsaleslineList" 
          style="width: 100%; margin-top: 10px;"
        >
          <el-table-column label="产品编码" width="120px" align="center" prop="itemCode" />
          <el-table-column label="产品名称" width="150px" align="center" prop="itemName" />
          <el-table-column label="规格型号" width="180px" align="center" prop="specification" :show-overflow-tooltip="true"/>
          <el-table-column label="出库数量" align="center" prop="quantitySales" width="120">
            <template slot-scope="scope">
              {{ scope.row.quantitySales }} {{ scope.row.unitName }}
            </template>
          </el-table-column>
          <el-table-column label="批次号" width="150px" align="center" prop="batchCode" />
          <el-table-column label="库位" width="200px" align="center" prop="locationInfo" :show-overflow-tooltip="true">
            <template slot-scope="scope">
              {{ scope.row.warehouseName }}/{{ scope.row.locationName }}/{{ scope.row.areaName }}
            </template>
          </el-table-column>
          <el-table-column label="防腐方案" width="120px" align="center" prop="antiCorrosionPlan">
            <template slot-scope="scope">
              {{ scope.row.antiCorrosionPlan || '--' }}
            </template>
          </el-table-column>
          <el-table-column label="备注" align="center" prop="remark" :show-overflow-tooltip="true"/>
          <el-table-column label="操作" width="120px" align="center" class-name="small-padding fixed-width" v-if="optType != 'view'">
            <template slot-scope="scope">
              <el-button
                size="mini"
                type="text"
                icon="el-icon-delete"
                @click="handleDeleteMaterial(scope.row)"
              >删除</el-button>
            </template>
          </el-table-column>
        </el-table>
        
        <!-- 空状态提示 -->
        <div v-if="productsaleslineList.length === 0 && !materialLoading" class="empty-material">
          <el-empty description="暂无出库物料" :image-size="80">
            <el-button 
              type="primary" 
              size="small"
              @click="handleAddMaterial"
              :disabled="!form.salesId"
              v-if="optType !== 'view'"
            >
              添加出库产品
            </el-button>
          </el-empty>
        </div>
      </el-card>
      
      <!-- 底部操作按钮 -->
      <div slot="footer" class="dialog-footer">
        <el-button 
          type="primary" 
          @click="saveAll" 
          v-if="optType !== 'view' && form.status =='PREPARE'"
          :loading="saveLoading"
        >
          保存出库单
        </el-button>
        <el-button type="warning" @click="submitToExecute" v-if="showSubmitButton">提交出库</el-button>
        <el-button @click="close">关 闭</el-button>
      </div>
    </el-dialog>

    <!-- 选择操作人员对话框 -->
    <el-dialog title="选择出库人" :visible.sync="operatorOpen" width="600px" append-to-body>
      <el-table v-loading="operatorLoading" :data="userList" @row-click="handleUserSelect">
        <el-table-column property="nickName" label="姓名" align="center"></el-table-column>
        <el-table-column property="userName" label="用户名" align="center"></el-table-column>
        <el-table-column property="deptName" label="部门" align="center"></el-table-column>
      </el-table>
      <div slot="footer" class="dialog-footer">
        <el-button @click="operatorOpen = false">取 消</el-button>
      </div>
    </el-dialog>

    <!-- 产品选择对话框 -->
    <el-dialog title="选择产品" :visible.sync="productSelectOpen" width="1000px" append-to-body>
      <div class="product-search" style="margin-bottom: 15px;">
        <el-input
          v-model="productSearchKeyword"
          placeholder="搜索产品编码、产品名称、规格型号..."
          style="width: 300px"
          clearable
          @input="handleProductSearch"
        >
          <el-button slot="append" icon="el-icon-search" @click="searchProducts"></el-button>
        </el-input>
      </div>

      <el-table 
        :data="filteredProductList" 
        v-loading="productLoading"
        style="width: 100%"
        max-height="400"
        border
        @row-click="handleProductRowClick"
      >
        <el-table-column label="产品编码" width="120" align="center" prop="itemCode" />
        <el-table-column label="产品名称" width="150" align="center" prop="itemName" :show-overflow-tooltip="true"/>
        <el-table-column label="规格型号" align="center" prop="specification" :show-overflow-tooltip="true"/>
        <el-table-column label="物料类型" width="100" align="center">
          <template slot-scope="scope">
            {{ getMaterialTypeText(scope.row) }}
          </template>
        </el-table-column>
        <el-table-column label="操作" width="80" align="center">
          <template slot-scope="scope">
            <el-button type="text" size="mini" @click="selectProduct(scope.row)">选择</el-button>
          </template>
        </el-table-column>
      </el-table>

      <div slot="footer" class="dialog-footer">
        <el-button @click="productSelectOpen = false">取消</el-button>
      </div>
    </el-dialog>
  </div>
</template>
<script>
import { listProductsales, getProductsales, delProductsales, addProductsales, updateProductsales, execute } from "@/api/mes/wm/productsales";
import { listProductsalesline, delProductsalesline, addProductsalesline } from "@/api/mes/wm/productsalesline"; 
import { listSendRecord } from "@/api/mes/wm/salesnotice";
import { listUser } from "@/api/system/user";
import { listWmstock } from "@/api/mes/wm/wmstock";
import {genCode} from "@/api/system/autocode/rule";
import request from '@/utils/request'

export default {
  name: "Productsales",
  dicts: ['mes_product_sales_status', 'anti_corrosion_plan'],
  data() {
    return {
      saveLoading: false,
      requiredQuantities: {},
      basicInfoLoading: false,
      operatorOpen: false,
      operatorLoading: false,
      userList: [],
      
      receivedOrderList: [],
      currentSendRecord: null,
      orderLoading: false,
      
      showMaterialForm: false,
      currentStep: 1,
      productForm: {
        itemCode: '',
        itemName: '',
        specification: ''
      },
      productRules: {
        itemCode: [
          { required: true, message: "请选择物料编码", trigger: "change" }
        ]
      },
      productSelectOpen: false,
      productList: [],
      filteredProductList: [],
      productSearchKeyword: '',
      productLoading: false,
      inventoryList: [],
      filteredInventoryList: [],
      inventoryLoading: false,
      inventorySearchKeyword: '',
      selectedBatches: [],
      batchFormList: [],
      materialLoading: false,
      productsaleslineList: [],

      autoGenFlag: true,
      optType: undefined,
      loading: true,
      ids: [],
      single: true,
      multiple: true,
      showSearch: true,
      total: 0,
      productsalesList: [],
      title: "",
      open: false,
      queryParams: {
        pageNum: 1,
        pageSize: 10,
        salesCode: null,
        soCode: null,
        clientNick: null,
        productModel: null,
      },
      form: {
        salesId: null,
        salesCode: null,
        salesName: null,
        soCode: null,
        clientNick: null,
        productInfo: null,
        antiCorrosionPlan: null,
        productSpecialNote: null,
        packageSpecialNote: null,
        otherSpecialNote: null,
        receiverInfo: null,
        salesDate: new Date(),
        status: "PREPARE",
        createBy: this.$store.state.user.name,
        remark: null
      },
      rules: {
        soCode: [
          { required: true, message: "请选择销售订单", trigger: "change" }
        ],
        salesCode: [
          { required: true, message: "出库单号不能为空", trigger: "blur" }
        ],
        salesDate: [
          { required: true, message: "请选择出库日期", trigger: "change" }
        ],
        createBy: [
          { required: true, message: "出库人不能为空", trigger: "blur" }
        ]
      }
    };
  },

  computed: {
    showSubmitButton() {
      return this.form.salesId && this.form.status === 'PREPARE' && this.optType !== 'view' && this.productsaleslineList.length > 0;
    }
  },
  
  watch: {
    'form.salesId': {
      handler(newVal) {
        if (newVal) {
          this.getMaterialList();
        } else {
          this.productsaleslineList = [];
          this.materialLoading = false;
        }
      },
      immediate: true
    }
  },
  created() {
    this.getSalesList();
    this.getReceivedOrders();
    this.getProductList();
    this.form.createBy = this.$store.state.user.name;
  },
  methods: {
    saveBasicInfo() {
      this.$refs["form"].validate(valid => {
        if (valid) {
          this.basicInfoLoading = true;
          const salesName = this.generateSalesName();
          
          const formData = {
            ...this.form,
            salesName: salesName
          };
          
          if (this.form.salesId != null) {
            updateProductsales(formData).then(response => {
              this.$modal.msgSuccess("基本信息更新成功");
              this.basicInfoLoading = false;
              this.getMaterialList();
            }).catch(() => {
              this.basicInfoLoading = false;
            });
          } else {
            addProductsales(formData).then(response => {
              let salesId = null;
              if (typeof response.data === 'number') {
                salesId = response.data;
              } else if (response.data && response.data.salesId) {
                salesId = response.data.salesId;
              } else if (response.data && response.data.id) {
                salesId = response.data.id;
              } else if (response.data) {
                salesId = response.data;
              }
              
              if (salesId) {
                this.form.salesId = salesId;
                this.$modal.msgSuccess("基本信息保存成功");
                this.optType = "edit";
                this.title = "修改销售出库单";
                this.basicInfoLoading = false;
                this.getMaterialList();
              } else {
                throw new Error('无法获取salesId');
              }
              
            }).catch((error) => {
              this.basicInfoLoading = false;
              this.$modal.msgError("保存失败: " + (error.message || '未知错误'));
            });
          }
        }
      });
    },

    saveAll() {
      if (!this.form.salesId) {
        this.$message.warning('请先保存基本信息');
        return;
      }
      
      if (this.productsaleslineList.length === 0) {
        this.$message.warning('请至少添加一个出库物料');
        return;
      }
      
      this.saveLoading = true;
      
      const formData = {
        ...this.form,
        salesName: this.generateSalesName()
      };
      
      updateProductsales(formData).then(response => {
        this.$modal.msgSuccess("出库单保存成功");
        this.saveLoading = false;
        this.open = false;
        this.getSalesList();
      }).catch(error => {
        this.saveLoading = false;
        this.$modal.msgError("保存失败");
      });
    },
    
    generateSalesName() {
      const date = new Date();
      const dateStr = `${date.getFullYear().toString().slice(2)}${(date.getMonth() + 1).toString().padStart(2, '0')}${date.getDate().toString().padStart(2, '0')}`;
      
      let clientPart = '';
      if (this.form.clientNick) {
        clientPart = this.form.clientNick;
      }
      
      let productPart = '';
      if (this.form.productInfo) {
        const productModels = this.extractProductModels(this.form.productInfo);
        if (productModels.length > 0) {
          if (productModels.length <= 2) {
            productPart = productModels.join('、');
          } else {
            productPart = `${productModels.slice(0, 2).join('、')}...`;
          }
        }
      }
      
      let name = dateStr;
      if (clientPart) {
        name += clientPart;
      }
      if (productPart) {
        name += productPart;
      }
      name += '出库单';
      
      return name;
    },
    
    extractProductModels(productInfo) {
      if (!productInfo) return [];
      
      const models = [];
      const lines = productInfo.split('；');
      
      for (const line of lines) {
        const parts = line.split('：');
        if (parts.length >= 2) {
          const modelPart = parts[1].split('，')[0];
          if (modelPart && modelPart.trim()) {
            models.push(modelPart.trim());
          }
        }
      }
      
      return models;
    },

    handleAddMaterial() {
      if (!this.form.salesId) {
        this.$message.warning('请先保存出库单基本信息');
        return;
      }
      this.showMaterialForm = true;
      this.currentStep = 1;
      this.resetMaterialForm();
    },
    
    cancelMaterialAdd() {
      this.showMaterialForm = false;
      this.resetMaterialForm();
    },
    
    resetMaterialForm() {
      this.productForm = {
        itemCode: '',
        itemName: '',
        specification: ''
      };
      this.inventoryList = [];
      this.filteredInventoryList = [];
      this.selectedBatches = [];
      this.batchFormList = [];
      this.inventorySearchKeyword = '';
      if (this.$refs.productForm) {
        this.$refs.productForm.clearValidate();
      }
    },

    getProductList() {
      this.productLoading = true;
      listWmstock({ pageSize: 2000 }).then(response => {
        const uniqueProducts = [];
        const productCodeSet = new Set();
        
        response.rows.forEach(item => {
          const isFinishedProduct = item.itemTypeId && (
            item.itemTypeId == 276 || 
            item.itemTypeId == 277
          );
          
          if (item.itemCode && !productCodeSet.has(item.itemCode) && isFinishedProduct) {
            productCodeSet.add(item.itemCode);
            uniqueProducts.push({
              itemCode: item.itemCode,
              itemName: item.itemName,
              specification: item.specification,
              itemTypeId: item.itemTypeId,
              itemTypeName: item.itemTypeName
            });
          }
        });
        
        this.productList = uniqueProducts;
        this.filteredProductList = uniqueProducts;
        this.productLoading = false;
      }).catch((error) => {
        this.productLoading = false;
        this.productList = [];
        this.filteredProductList = [];
      });
    },

    handleProductSelect() {
      this.productSelectOpen = true;
      this.productSearchKeyword = '';
      this.filteredProductList = this.productList;
    },

    handleProductSearch() {
      if (!this.productSearchKeyword) {
        this.filteredProductList = this.productList;
        return;
      }
      
      const keyword = this.productSearchKeyword.toLowerCase().trim();
      const baseModel = this.extractBaseModel(keyword);
      
      this.filteredProductList = this.productList.filter(item => {
        if ((item.itemCode && item.itemCode.toLowerCase().includes(keyword)) ||
            (item.itemName && item.itemName.toLowerCase().includes(keyword))) {
          return true;
        }
        
        if (item.specification) {
          const specLower = item.specification.toLowerCase();
          
          if (specLower.includes(keyword)) {
            return true;
          }
          
          if (baseModel && baseModel !== keyword) {
            const productBaseModel = this.extractBaseModel(item.specification);
            if (productBaseModel && productBaseModel.includes(baseModel)) {
              return true;
            }
          }
          
          if (baseModel && specLower.includes(baseModel)) {
            return true;
          }
        }
        
        return false;
      });
    },

    extractBaseModel(model) {
      if (!model) return '';
      
      const baseModel = model
        .replace(/\s*V\d+.*$/i, '')
        .replace(/\s*版本\d+.*$/i, '')
        .replace(/\s*Ver\.?\s*\d+.*$/i, '')
        .replace(/\s*第\d+[代版].*$/i, '')
        .replace(/\s*[\(（].*[\)）].*$/, '')
        .trim();
      
      return baseModel || model;
    },

    searchProducts() {
      this.handleProductSearch();
    },

    handleProductRowClick(row) {
      this.selectProduct(row);
    },

    selectProduct(product) {
      this.productForm.itemCode = product.itemCode;
      this.productForm.itemName = product.itemName;
      this.productForm.specification = product.specification;
      this.productSelectOpen = false;
      this.searchInventory();
    },

    searchInventory() {
      if (!this.productForm.itemCode) {
        this.$message.warning('请先选择物料编码');
        return;
      }

      this.inventoryLoading = true;
      
      const selectedSpecification = this.productForm.specification;
      const baseModel = this.extractBaseModel(selectedSpecification);
      
      listWmstock({ 
        itemCode: this.productForm.itemCode,
        pageSize: 500 
      }).then(response => {
        let inventoryData = response.rows;
        
        if (baseModel) {
          inventoryData = response.rows.filter(item => {
            if (!item.specification) return false;
            const itemBaseModel = this.extractBaseModel(item.specification);
            return itemBaseModel && itemBaseModel.includes(baseModel);
          });
        }
        
        this.inventoryList = inventoryData.map(item => ({
          ...item,
          locationInfo: `${item.warehouseName}/${item.locationName}/${item.areaName}`,
          itemId: item.itemId || item.materialStockId
        }));
        this.filteredInventoryList = this.inventoryList;
        this.inventoryLoading = false;
      }).catch(() => {
        this.inventoryLoading = false;
        this.filteredInventoryList = [];
      });
    },

    handleInventorySearch() {
      if (!this.inventorySearchKeyword) {
        this.filteredInventoryList = this.inventoryList;
        return;
      }
      
      const keyword = this.inventorySearchKeyword.toLowerCase();
      this.filteredInventoryList = this.inventoryList.filter(item => 
        (item.specification && item.specification.toLowerCase().includes(keyword)) ||
        (item.batchCode && item.batchCode.toLowerCase().includes(keyword)) ||
        (item.warehouseName && item.warehouseName.toLowerCase().includes(keyword)) ||
        (item.locationName && item.locationName.toLowerCase().includes(keyword)) ||
        (item.areaName && item.areaName.toLowerCase().includes(keyword))
      );
    },

    getMaterialTypeText(row) {
      if (row.itemTypeId == 276) return '半成品';
      if (row.itemTypeId == 277) return '成品';
      if (row.itemTypeName) {
        if (row.itemTypeName.includes('成品')) return '成品';
        if (row.itemTypeName.includes('半成品')) return '半成品';
        return row.itemTypeName;
      }
      return '--';
    },

    handleBatchSelectionChange(selection) {
      this.selectedBatches = selection;
    },

    goToStep2() {
      this.batchFormList = this.selectedBatches.map(batch => ({
        materialStockId: batch.materialStockId,
        itemId: batch.itemId,
        itemCode: batch.itemCode,
        itemName: batch.itemName,
        specification: batch.specification,
        unitName: batch.unitName,
        batchCode: batch.batchCode,
        quantityOnhand: parseFloat(batch.quantityOnhand) || 0,
        quantitySales: 0,
        warehouseId: batch.warehouseId,
        warehouseName: batch.warehouseName || batch.warehouseCode || '', 
        locationId: batch.locationId,
        locationName: batch.locationName || batch.locationCode || '',
        areaId: batch.areaId,
        areaName: batch.areaName || batch.areaCode || '',
        antiCorrosionPlan: batch.antiCorrosionPlan,
        remark: ''
      }));
      this.currentStep = 2;
    },

    validateQuantity(row) {
      if (row.quantitySales > row.quantityOnhand) {
        this.$message.warning('出库数量不能大于在库数量');
        row.quantitySales = row.quantityOnhand;
      }
    },

    submitBatchForm() {
      const invalidRows = this.batchFormList.filter(row => 
        row.quantitySales <= 0 || row.quantitySales > row.quantityOnhand
      );
      
      if (invalidRows.length > 0) {
        this.$message.warning('请检查出库数量，不能为0且不能大于在库数量');
        return;
      }
    
      const productSales = {};
      this.batchFormList.forEach(batch => {
        const spec = batch.specification;
        if (!productSales[spec]) {
          productSales[spec] = 0;
        }
        productSales[spec] += parseFloat(batch.quantitySales) || 0;
      });
    
      let hasExceeded = false;
      for (const [specification, totalSales] of Object.entries(productSales)) {
        const requiredQty = this.productRequirements[specification];
        
        if (requiredQty && totalSales > requiredQty) {
          this.$message.warning(`产品 ${specification} 出库数量 ${totalSales} 不能超过需求数量 ${requiredQty}`);
          hasExceeded = true;
        }
      }
      
      if (hasExceeded) {
        return;
      }
    
      const promises = this.batchFormList.map(batch => {
        const formData = {
          salesId: this.form.salesId,
          itemId: batch.itemId || batch.materialStockId,
          itemCode: batch.itemCode,
          itemName: batch.itemName,
          specification: batch.specification,
          unitName: batch.unitName,
          quantitySales: batch.quantitySales,
          batchCode: batch.batchCode,
          warehouseId: batch.warehouseId,
          warehouseName: batch.warehouseName || '默认仓库',
          locationId: batch.locationId,
          locationName: batch.locationName || '默认库区',
          areaId: batch.areaId,
          areaName: batch.areaName || '默认库位',
          antiCorrosionPlan: batch.antiCorrosionPlan,
          remark: batch.remark
        };
        
        return addProductsalesline(formData);
      });
    
      Promise.all(promises).then(() => {
        this.$modal.msgSuccess("新增成功");
        this.showMaterialForm = false;
        this.resetMaterialForm();
        this.getMaterialList();
      }).catch(error => {
        this.$modal.msgError("新增失败: " + (error.message || '未知错误'));
      });
    },
    
    getRequiredQuantity() {
      return null;
    },

    handleDeleteMaterial(row) {
      const lineIds = row.lineId;
      this.$modal.confirm('是否确认删除该出库物料？').then(function() {
        return delProductsalesline(lineIds);
      }).then(() => {
        this.getMaterialList();
        this.$modal.msgSuccess("删除成功");
      }).catch(() => {});
    },

    getQuantityClass(quantity) {
      const num = parseFloat(quantity) || 0;
      if (num > 100) return 'quantity-high';
      if (num > 50) return 'quantity-medium';
      return 'quantity-low';
    },

    getMaterialList() {
      if (!this.form.salesId) {
        this.productsaleslineList = [];
        this.materialLoading = false;
        return;
      }
      
      this.materialLoading = true;
      listProductsalesline({ salesId: this.form.salesId }).then(response => {
        this.productsaleslineList = response.rows || [];
        this.materialLoading = false;
      }).catch(error => {
        this.materialLoading = false;
        this.productsaleslineList = [];
        this.$modal.msgError("加载物料列表失败");
      });
    },

    handleSelectOperator() {
      this.operatorOpen = true;
      this.operatorLoading = true;
      listUser({ pageSize: 1000 }).then(response => {
        this.userList = response.rows;
        this.operatorLoading = false;
      });
    },

    handleUserSelect(row) {
      this.form.createBy = row.nickName;
      this.operatorOpen = false;
    },

    getSalesList() {
      this.loading = true;
      listProductsales(this.queryParams).then(response => {
        this.productsalesList = response.rows.map(item => {
          if (item.productItems && item.productItems.length > 0) {
            item.outboundContent = item.productItems.map(product => 
              `${product.batchCode}，${product.itemName}：${product.quantitySales}${product.unitName}`
            ).join('；');
          } else {
            item.outboundContent = '暂无出库物料';
          }
          return item;
        });
        this.total = response.total;
        this.loading = false;
      });
    },

    getReceivedOrders() {
      return new Promise((resolve, reject) => {
        this.orderLoading = true;
        
        request({
          url: '/mes/sales/order/send/listWithDetails',
          method: 'get',
          params: {
            sendStatus: 'RECEIVED',
            pageSize: 1000
          }
        }).then(response => {
          const uniqueOrders = [];
          const orderCodeSet = new Set();
          
          if (response.rows && response.rows.length > 0) {
            response.rows.forEach(record => {
              if (record.orderCode && !orderCodeSet.has(record.orderCode)) {
                orderCodeSet.add(record.orderCode);
                
                const order = {
                  orderCode: record.orderCode,
                  customerNick: record.customerNick,
                  productInfo: record.productInfo,
                  antiCorrosionPlan: record.antiCorrosionPlan,
                  productSpecialNote: record.productSpecialNote,
                  packageSpecialNote: record.packageSpecialNote,
                  otherSpecialNote: record.otherSpecialNote,
                  shippingAddress: record.shippingAddress,
                  sendRecord: record,
                  productDetails: record.productDetails || []
                };
                
                uniqueOrders.push(order);
              }
            });
          }
          
          this.receivedOrderList = uniqueOrders;
          this.orderLoading = false;
          resolve(uniqueOrders);
        }).catch(error => {
          this.orderLoading = false;
          this.receivedOrderList = [];
          reject(error);
        });
      });
    },

    handleOrderSelect(orderCode) {
      const selectedOrder = this.receivedOrderList.find(order => order.orderCode === orderCode);
      if (selectedOrder) {
        this.form.soCode = selectedOrder.orderCode;
        this.form.clientNick = selectedOrder.customerNick;
        this.form.productInfo = selectedOrder.productInfo;
        this.form.antiCorrosionPlan = selectedOrder.antiCorrosionPlan;
        this.form.productSpecialNote = selectedOrder.productSpecialNote;
        this.form.packageSpecialNote = selectedOrder.packageSpecialNote;
        this.form.otherSpecialNote = selectedOrder.otherSpecialNote;
        this.form.receiverInfo = selectedOrder.shippingAddress;
        this.currentSendRecord = selectedOrder.sendRecord;
        
        this.productRequirements = this.calculateProductRequirements(selectedOrder.productDetails);
        
        this.form.salesName = this.generateSalesName();
        
        if (this.autoGenFlag) {
          this.generateSalesCode(selectedOrder.orderCode);
        }
      } else {
        this.clearOrderInfo();
      }
    },
    
    calculateProductRequirements(productDetails) {
      const requirements = {};
      
      if (productDetails && productDetails.length > 0) {
        productDetails.forEach(product => {
          if (product.specification) {
            requirements[product.specification] = product.quantityRequired || 0;
          }
        });
      }
      
      return requirements;
    },

    clearOrderInfo() {
      this.form.soCode = null;
      this.form.clientNick = null;
      this.form.productInfo = null;
      this.form.antiCorrosionPlan = null;
      this.form.productSpecialNote = null;
      this.form.packageSpecialNote = null;
      this.form.otherSpecialNote = null;
      this.form.receiverInfo = null;
      this.currentSendRecord = null;
      this.productRequirements = {};
      this.form.salesName = this.generateSalesName();
    },

    generateSalesCode(orderCode) {
      if (orderCode) {
        const randomNum = Math.floor(Math.random() * 10);
        this.form.salesCode = `${orderCode}-OUT${randomNum}`;
      }
    },

    close() {
      this.open = false;
      this.reset();
    },

    reset() {
      this.form = {
        salesId: null,
        salesCode: null,
        salesName: null,
        soCode: null,
        clientNick: null,
        productInfo: null,
        antiCorrosionPlan: null,
        productSpecialNote: null,
        packageSpecialNote: null,
        otherSpecialNote: null,
        receiverInfo: null,
        salesDate: new Date(),
        status: "PREPARE",
        createBy: this.$store.state.user.name,
        remark: null
      };
      this.autoGenFlag = true;
      this.showMaterialForm = false;
      this.currentSendRecord = null;
      this.resetForm("form");
    },

    handleQuery() {
      this.queryParams.pageNum = 1;
      this.getSalesList();
    },

    resetQuery() {
      this.resetForm("queryForm");
      this.handleQuery();
    },

    handleSelectionChange(selection) {
      this.ids = selection.map(item => item.salesId)
      this.single = selection.length!==1
      this.multiple = !selection.length
    },

    handleView(row){
      this.reset();
      const salesId = row.salesId
      getProductsales(salesId).then(response => {
        this.form = response.data;
        this.open = true;
        this.title = "查看出库单详情";
        this.optType = "view";
        
        this.getReceivedOrders().then(() => {
          if (this.form.soCode) {
            this.setCurrentSendRecord(this.form.soCode);
          }
        });
      });
    },
    
    handleUpdate(row) {
      this.reset();
      const salesId = row.salesId || this.ids
      getProductsales(salesId).then(response => {
        this.form = response.data;
        this.open = true;
        this.title = "修改销售出库单";
        this.optType = "edit";
        this.autoGenFlag = !this.form.salesCode;
        
        this.getReceivedOrders().then(() => {
          if (this.form.soCode) {
            this.setCurrentSendRecord(this.form.soCode);
          }
          this.getMaterialList();
        });
      });
    },
    
    setCurrentSendRecord(orderCode) {
      const selectedOrder = this.receivedOrderList.find(order => order.orderCode === orderCode);
      if (selectedOrder && selectedOrder.sendRecord) {
        this.currentSendRecord = selectedOrder.sendRecord;
        this.form.productInfo = this.currentSendRecord.productInfo;
        this.form.antiCorrosionPlan = this.currentSendRecord.antiCorrosionPlan;
        this.form.productSpecialNote = this.currentSendRecord.productSpecialNote;
        this.form.packageSpecialNote = this.currentSendRecord.packageSpecialNote;
        this.form.otherSpecialNote = this.currentSendRecord.otherSpecialNote;
      } else {
        this.fetchSendRecordByOrderCode(orderCode);
      }
    },
    
    fetchSendRecordByOrderCode(orderCode) {
      request({
        url: '/mes/sales/order/send/listWithDetails',
        method: 'get',
        params: {
          orderCode: orderCode,
          sendStatus: 'RECEIVED'
        }
      }).then(response => {
        if (response.rows && response.rows.length > 0) {
          this.currentSendRecord = response.rows[0];
          this.form.productInfo = this.currentSendRecord.productInfo;
          this.form.antiCorrosionPlan = this.currentSendRecord.antiCorrosionPlan;
        }
      });
    },

    handleAdd() {
      this.reset();
      this.open = true;
      this.title = "新增销售出库单";
      this.optType = "add";
      this.autoGenFlag = true;
      this.form.salesName = this.generateSalesName();
      this.getReceivedOrders();
      this.productsaleslineList = [];
    },

    submitToExecute(){
      this.$refs["form"].validate(valid => {
        if (valid) {
          this.form.status = 'UNEXECUTE';
          updateProductsales(this.form).then(response => {
            this.$modal.msgSuccess("提交成功，请在列表执行出库");
            this.open = false;
            this.getSalesList();
          }).catch(() => {
            this.form.status = 'PREPARE';
          });
        }
      });
    },

    handleDelete(row) {
      const salesIds = row.salesId || this.ids;
      this.$modal.confirm('是否确认删除销售出库单"' + salesIds + '"？').then(() => {
        return delProductsales(salesIds);
      }).then(() => {
        this.getSalesList();
        this.$modal.msgSuccess("删除成功");
      }).catch((error) => {
        if (error.response && error.response.data) {
          this.$modal.msgError("删除失败: " + error.response.data.msg);
        } else if (error.msg) {
          this.$modal.msgError("删除失败: " + error.msg);
        } else {
          this.$modal.msgError("删除失败，请检查单据状态");
        }
      });
    },

    // 执行出库
    handleExecute(row){
      const salesIds = row.salesId || this.ids;
      this.$modal.confirm('确认执行出库？').then(() => {
        // 正确的调用方式 - 只需要传递 salesId
        return execute(salesIds);
      }).then(() => {
        this.getSalesList();
        this.$modal.msgSuccess("出库成功");
      }).catch((error) => {
        console.error('出库失败:', error);
        // 显示具体的错误信息
        if (error.response && error.response.data) {
          this.$modal.msgError("出库失败: " + error.response.data.msg);
        } else if (error.msg) {
          this.$modal.msgError("出库失败: " + error.msg);
        } else {
          this.$modal.msgError("出库失败，请检查单据状态");
        }
      });
    },

    handleAutoGenChange(autoGenFlag){
      if(autoGenFlag){
        if (this.form.soCode) {
          this.generateSalesCode(this.form.soCode);
        } else {
          genCode('PRODUCTSALSE_CODE').then(response =>{
            this.form.salesCode = response;
          });
        }
      }else{
        this.form.salesCode = null;
      }
    }
  }
};
</script>

<style scoped>
.order-info-card {
  margin-bottom: 20px;
  border: 1px solid #e6ebf5;
  border-radius: 4px;
}

.order-info-title {
  font-weight: bold;
  color: #409EFF;
  font-size: 16px;
}

.info-item {
  margin-bottom: 12px;
  line-height: 1.6;
  display: flex;
  align-items: flex-start;
}

.info-item label {
  font-weight: bold;
  color: #606266;
  min-width: 120px;
  display: inline-block;
  flex-shrink: 0;
}

.info-value {
  color: #303133;
  flex: 1;
}

.product-info-highlight {
  color: #E6A23C;
  font-weight: bold;
  background-color: #fdf6ec;
  padding: 4px 8px;
  border-radius: 4px;
  border: 1px solid #f5dab1;
  display: inline-block;
}

.anti-corrosion-highlight {
  color: #409EFF;
  font-weight: bold;
  background-color: #f0f9ff;
  padding: 4px 8px;
  border-radius: 4px;
  border: 1px solid #b3d8ff;
  display: inline-block;
}

.special-requirement {
  color: #F56C6C;
  font-weight: bold;
  background-color: #fef0f0;
  padding: 6px 10px;
  border-radius: 4px;
  border: 1px solid #fbc4c4;
  display: block;
  margin-top: 4px;
  line-height: 1.4;
}

.order-tip {
  font-size: 12px;
  color: #909399;
  margin-top: 5px;
  font-style: italic;
}

.outbound-content {
  white-space: pre-line;
  line-height: 1.5;
}

/* 调整开关样式 */
:deep(.el-switch) {
  margin-left: 10px;
}

:deep(.el-switch__label) {
  color: #909399;
}

:deep(.el-switch__label.is-active) {
  color: #409EFF;
}

/* 防腐方案字典标签样式 */
:deep(.anti-corrosion-highlight .el-tag) {
  border: none !important;
  background-color: transparent !important;
  color: #409EFF !important;
  font-weight: bold;
  padding: 0;
}

/* 物料相关样式 */
.step-content {
  min-height: 400px;
}

.section-title {
  font-size: 16px;
  font-weight: bold;
  color: #303133;
  margin-bottom: 16px;
  padding-bottom: 8px;
  border-bottom: 1px solid #e4e7ed;
}

.batch-selection-section {
  margin-top: 20px;
}

.step-actions {
  margin-top: 20px;
  text-align: center;
}

.quantity-high {
  color: #67c23a;
  font-weight: bold;
}

.quantity-medium {
  color: #e6a23c;
  font-weight: bold;
}

.quantity-low {
  color: #f56c6c;
  font-weight: bold;
}

.inventory-search {
  margin-bottom: 15px;
}

.product-search {
  margin-bottom: 15px;
}

.material-form-section {
  border: 1px solid #e4e7ed;
  border-radius: 4px;
  padding: 15px;
  margin-bottom: 15px;
  background-color: #f8f9fa;
}

/* 新增样式 */
.save-tip {
  font-size: 12px;
  color: #67c23a;
  font-weight: bold;
}

.material-tip {
  font-size: 13px;
  color: #909399;
}

.dialog-footer {
  display: flex;
  justify-content: flex-end;
  align-items: center;
  padding: 20px 0 0;
  border-top: 1px solid #e4e7ed;
  margin-top: 20px;
}

/* 调整布局样式 */
:deep(.el-form-item__content) {
  display: flex;
  align-items: center;
}

:deep(.el-form-item__label) {
  line-height: 32px;
}

/* 出库内容列设置为弹性 */
.outbound-content {
  white-space: pre-line;
  line-height: 1.5;
  min-width: 200px;
}

/* 确保物料操作按钮始终显示 */
.material-operations {
  display: flex !important;
  align-items: center;
  margin-bottom: 15px;
  padding: 10px;
  background-color: #f8f9fa;
  border-radius: 4px;
  min-height: 50px;
}

/* 按钮禁用状态样式 */
.material-operations .el-button[disabled] {
  background-color: #f5f7fa;
  border-color: #e4e7ed;
  color: #c0c4cc;
}

/* 空状态样式 */
.empty-material {
  padding: 40px 0;
  text-align: center;
  border: 1px dashed #e4e7ed;
  border-radius: 4px;
  margin: 10px 0;
  background-color: #fafafa;
}

/* 卡片头部样式优化 */
:deep(.el-card__header) {
  padding: 12px 20px;
  background-color: #f8f9fa;
  border-bottom: 1px solid #e6ebf5;
}

/* 特殊要求区域的额外样式 */
.special-requirements-section {
  margin-top: 15px;
  padding: 10px;
  background-color: #f8f9fa;
  border-radius: 4px;
  border-left: 4px solid #409EFF;
}

.special-requirement-item {
  margin-bottom: 8px;
  padding: 8px;
  background: white;
  border-radius: 4px;
  border: 1px solid #e4e7ed;
}

/* 响应式调整 */
@media (max-width: 768px) {
  .info-item {
    flex-direction: column;
    align-items: flex-start;
  }
  
  .info-item label {
    margin-bottom: 4px;
  }
}
</style>
---------------------
订单发送和发货通知单页用的数据库：
-- phpMyAdmin SQL Dump
-- version 5.0.4
-- https://www.phpmyadmin.net/
--
-- 主机： localhost
-- 生成日期： 2025-10-29 13:39:15
-- 服务器版本： 5.7.40-log
-- PHP 版本： 7.4.33

SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
START TRANSACTION;
SET time_zone = "+00:00";


/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;

--
-- 数据库： `mes_db`
--

-- --------------------------------------------------------

--
-- 表的结构 `sales_order_send_record`
--

CREATE TABLE `sales_order_send_record` (
  `record_id` bigint(20) NOT NULL COMMENT '记录ID',
  `notice_code` varchar(100) DEFAULT NULL COMMENT '通知单编号',
  `notice_name` varchar(200) DEFAULT NULL COMMENT '通知单名称',
  `order_id` bigint(20) NOT NULL COMMENT '订单ID',
  `order_code` varchar(100) DEFAULT NULL COMMENT '销售订单编号',
  `contract_id` bigint(20) DEFAULT NULL COMMENT '合同ID',
  `contract_code` varchar(100) DEFAULT NULL COMMENT '合同编号',
  `customer_id` bigint(20) DEFAULT NULL COMMENT '客户ID',
  `customer_nick` varchar(100) DEFAULT NULL COMMENT '客户简称',
  `product_info` text COMMENT '产品信息',
  `anti_corrosion_plan` varchar(50) DEFAULT NULL COMMENT '防腐方案',
  `anti_corrosion_note` varchar(500) DEFAULT NULL COMMENT '防腐说明',
  `product_special_note` text COMMENT '产品特殊情况',
  `package_special_note` text COMMENT '包装特殊情况',
  `other_special_note` text COMMENT '其他特殊情况',
  `shipping_date` date DEFAULT NULL COMMENT '发货日期',
  `shipping_address` text COMMENT '收货信息（完整地址，包含联系人信息）',
  `send_type` varchar(20) DEFAULT NULL COMMENT '发送类型：WAREHOUSE-仓库, PRODUCTION-生产',
  `send_status` varchar(20) DEFAULT 'SENT' COMMENT '发送状态：SENT-已发送, RECEIVED-已接收, COMPLETED-已完成',
  `sender_id` bigint(20) DEFAULT NULL COMMENT '发送人ID',
  `receiver_user_id` bigint(20) DEFAULT NULL COMMENT '接收人ID',
  `send_time` datetime DEFAULT NULL COMMENT '发送时间',
  `receive_time` datetime DEFAULT NULL COMMENT '接收时间',
  `create_by` varchar(64) DEFAULT '' COMMENT '创建者',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_by` varchar(64) DEFAULT '' COMMENT '更新者',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='订单发送记录表';

--
-- 转储表的索引
--

--
-- 表的索引 `sales_order_send_record`
--
ALTER TABLE `sales_order_send_record`
  ADD PRIMARY KEY (`record_id`),
  ADD UNIQUE KEY `uk_notice_code` (`notice_code`),
  ADD KEY `idx_order_id` (`order_id`),
  ADD KEY `idx_send_time` (`send_time`),
  ADD KEY `idx_send_status` (`send_status`),
  ADD KEY `idx_sender_id` (`sender_id`),
  ADD KEY `idx_receiver_user_id` (`receiver_user_id`);

--
-- 在导出的表使用AUTO_INCREMENT
--

--
-- 使用表AUTO_INCREMENT `sales_order_send_record`
--
ALTER TABLE `sales_order_send_record`
  MODIFY `record_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '记录ID';
COMMIT;

/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;

销售出库单页数据库：
-- phpMyAdmin SQL Dump
-- version 5.0.4
-- https://www.phpmyadmin.net/
--
-- 主机： localhost
-- 生成日期： 2025-10-29 13:41:07
-- 服务器版本： 5.7.40-log
-- PHP 版本： 7.4.33

SET SQL_MODE = "NO_AUTO_VALUE_ON_ZERO";
START TRANSACTION;
SET time_zone = "+00:00";


/*!40101 SET @OLD_CHARACTER_SET_CLIENT=@@CHARACTER_SET_CLIENT */;
/*!40101 SET @OLD_CHARACTER_SET_RESULTS=@@CHARACTER_SET_RESULTS */;
/*!40101 SET @OLD_COLLATION_CONNECTION=@@COLLATION_CONNECTION */;
/*!40101 SET NAMES utf8mb4 */;

--
-- 数据库： `mes_db`
--

-- --------------------------------------------------------

--
-- 表的结构 `wm_product_sales`
--

CREATE TABLE `wm_product_sales` (
  `sales_id` bigint(20) NOT NULL COMMENT '出库单ID',
  `sales_code` varchar(64) NOT NULL COMMENT '出库单编号',
  `sales_name` varchar(255) NOT NULL COMMENT '出库单名称',
  `notice_id` bigint(20) DEFAULT NULL COMMENT '通知单ID',
  `notice_code` varchar(64) DEFAULT NULL COMMENT '通知单编号',
  `so_code` varchar(64) DEFAULT NULL COMMENT '销售订单编号',
  `client_id` bigint(20) DEFAULT NULL COMMENT '客户ID',
  `client_code` varchar(64) DEFAULT NULL COMMENT '客户编码',
  `client_name` varchar(255) DEFAULT NULL COMMENT '客户名称',
  `client_nick` varchar(255) DEFAULT NULL COMMENT '客户简称',
  `recipient` varchar(128) DEFAULT NULL COMMENT '收货人',
  `tel` varchar(128) DEFAULT NULL COMMENT '联系方式',
  `address` varchar(256) DEFAULT NULL COMMENT '收货地址',
  `carrier` varchar(128) DEFAULT NULL COMMENT '承运商',
  `shipping_number` varchar(128) DEFAULT NULL COMMENT '运输单号',
  `sales_date` datetime DEFAULT NULL COMMENT '出库日期',
  `status` varchar(64) DEFAULT 'PREPARE' COMMENT '单据状态',
  `remark` varchar(500) DEFAULT '' COMMENT '备注',
  `attr1` varchar(64) DEFAULT NULL COMMENT '预留字段1',
  `attr2` varchar(255) DEFAULT NULL COMMENT '预留字段2',
  `attr3` int(11) DEFAULT '0' COMMENT '预留字段3',
  `attr4` int(11) DEFAULT '0' COMMENT '预留字段4',
  `create_by` varchar(64) DEFAULT '' COMMENT '创建者',
  `create_time` datetime DEFAULT NULL COMMENT '创建时间',
  `update_by` varchar(64) DEFAULT '' COMMENT '更新者',
  `update_time` datetime DEFAULT NULL COMMENT '更新时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='销售出库单表';

--
-- 转储表的索引
--

--
-- 表的索引 `wm_product_sales`
--
ALTER TABLE `wm_product_sales`
  ADD PRIMARY KEY (`sales_id`);

--
-- 在导出的表使用AUTO_INCREMENT
--

--
-- 使用表AUTO_INCREMENT `wm_product_sales`
--
ALTER TABLE `wm_product_sales`
  MODIFY `sales_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '出库单ID';
COMMIT;

/*!40101 SET CHARACTER_SET_CLIENT=@OLD_CHARACTER_SET_CLIENT */;
/*!40101 SET CHARACTER_SET_RESULTS=@OLD_CHARACTER_SET_RESULTS */;
/*!40101 SET COLLATION_CONNECTION=@OLD_COLLATION_CONNECTION */;
===================
订单发送页的后端文件：
// SalesOrderSendRecordController.java
package com.ktg.mes.sales.controller;

import java.util.List;
import javax.servlet.http.HttpServletResponse;

import com.ktg.common.utils.poi.ExcelUtil;
import com.ktg.mes.sales.domain.SalesOrderSendRecord;
import com.ktg.mes.sales.domain.vo.SalesOrderSendRecordVO;
import com.ktg.mes.sales.service.ISalesOrderSendRecordService;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.ktg.common.annotation.Log;
import com.ktg.common.core.controller.BaseController;
import com.ktg.common.core.domain.AjaxResult;
import com.ktg.common.enums.BusinessType;
import com.ktg.common.core.page.TableDataInfo;

/**
 * 订单发送记录Controller
 */
@RestController
@RequestMapping("/mes/sales/order/send")
public class SalesOrderSendRecordController extends BaseController {
    @Autowired
    private ISalesOrderSendRecordService salesOrderSendRecordService;

    /**
     * 查询订单发送记录列表
     */
    @PreAuthorize("@ss.hasPermi('mes:sales:order:send:list')")
    @GetMapping("/list")
    public TableDataInfo list(SalesOrderSendRecord salesOrderSendRecord) {
        startPage();
        List<SalesOrderSendRecord> list = salesOrderSendRecordService.selectSalesOrderSendRecordList(salesOrderSendRecord);
        return getDataTable(list);
    }

    /**
     * 导出订单发送记录列表
     */
    @PreAuthorize("@ss.hasPermi('mes:sales:order:send:export')")
    @Log(title = "订单发送记录", businessType = BusinessType.EXPORT)
    @PostMapping("/export")
    public void export(HttpServletResponse response, SalesOrderSendRecord salesOrderSendRecord) {
        List<SalesOrderSendRecord> list = salesOrderSendRecordService.selectSalesOrderSendRecordList(salesOrderSendRecord);
        ExcelUtil<SalesOrderSendRecord> util = new ExcelUtil<SalesOrderSendRecord>(SalesOrderSendRecord.class);
        util.exportExcel(response, list, "订单发送记录数据");
    }

    /**
     * 获取订单发送记录详细信息
     */
    @PreAuthorize("@ss.hasPermi('mes:sales:order:send:query')")
    @GetMapping(value = "/{recordId}")
    public AjaxResult getInfo(@PathVariable("recordId") Long recordId) {
        return AjaxResult.success(salesOrderSendRecordService.selectSalesOrderSendRecordByRecordId(recordId));
    }

    /**
     * 根据订单ID查询发送记录
     */
    @PreAuthorize("@ss.hasPermi('mes:sales:order:send:query')")
    @GetMapping("/order/{orderId}")
    public AjaxResult getRecordsByOrderId(@PathVariable("orderId") Long orderId) {
        List<SalesOrderSendRecord> list = salesOrderSendRecordService.selectSendRecordByOrderId(orderId);
        return AjaxResult.success(list);
    }

    /**
     * 新增订单发送记录
     */
    @PreAuthorize("@ss.hasPermi('mes:sales:order:send:add')")
    @Log(title = "订单发送记录", businessType = BusinessType.INSERT)
    @PostMapping
    public AjaxResult add(@RequestBody SalesOrderSendRecord salesOrderSendRecord) {
        SalesOrderSendRecord record = salesOrderSendRecordService.createSendRecord(salesOrderSendRecord, getUsername());
        return AjaxResult.success(record);
    }

    /**
     * 从订单创建发送记录
     */
    @PreAuthorize("@ss.hasPermi('mes:sales:order:send:add')")
    @Log(title = "订单发送记录", businessType = BusinessType.INSERT)
    @PostMapping("/createFromOrder")
    public AjaxResult createFromOrder(@RequestBody CreateSendRecordRequest request) {
        try {
            SalesOrderSendRecord record = salesOrderSendRecordService.createSendRecordFromOrder(
                request.getOrder(), 
                request.getSendType(), 
                request.getShippingDate(), 
                request.getShippingAddress(), 
                request.getRemark(), 
                getUsername(),
                request.getSenderId(),
                request.getReceiverUserId(),
                request.getProductDetails() // 新增：传递产品明细
            );
            return AjaxResult.success("发货通知创建成功", record);
        } catch (Exception e) {
            return AjaxResult.error("创建发货通知失败: " + e.getMessage());
        }
    }

    /**
     * 修改订单发送记录
     */
    @PreAuthorize("@ss.hasPermi('mes:sales:order:send:edit')")
    @Log(title = "订单发送记录", businessType = BusinessType.UPDATE)
    @PutMapping
    public AjaxResult edit(@RequestBody SalesOrderSendRecord salesOrderSendRecord) {
        return toAjax(salesOrderSendRecordService.updateSalesOrderSendRecord(salesOrderSendRecord));
    }

    /**
     * 删除订单发送记录
     */
    @PreAuthorize("@ss.hasPermi('mes:sales:order:send:remove')")
    @Log(title = "订单发送记录", businessType = BusinessType.DELETE)
    @DeleteMapping("/{recordIds}")
    public AjaxResult remove(@PathVariable Long[] recordIds) {
        return toAjax(salesOrderSendRecordService.deleteSalesOrderSendRecordByRecordIds(recordIds));
    }

    /**
     * 更新发送状态
     */
    @PreAuthorize("@ss.hasPermi('mes:sales:order:send:edit')")
    @Log(title = "订单发送记录", businessType = BusinessType.UPDATE)
    @PostMapping("/updateStatus")
    public AjaxResult updateStatus(@RequestBody UpdateStatusRequest request) {
        int result = salesOrderSendRecordService.updateSendStatus(
            request.getRecordId(), 
            request.getSendStatus(),
            request.getReceiverUserId() != null ? request.getReceiverUserId().toString() : null
        );
        return toAjax(result);
    }

    /**
     * 创建发送记录请求对象
     */
    public static class CreateSendRecordRequest {
        private com.ktg.mes.sales.domain.SalesOrder order;
        private String sendType;
        private java.util.Date shippingDate;
        private String shippingAddress;
        private String remark;
        private Long senderId;
        private Long receiverUserId;
        private List<ProductDetail> productDetails; // 新增：产品明细

        // getter and setter methods
        public com.ktg.mes.sales.domain.SalesOrder getOrder() { return order; }
        public void setOrder(com.ktg.mes.sales.domain.SalesOrder order) { this.order = order; }
        
        public String getSendType() { return sendType; }
        public void setSendType(String sendType) { this.sendType = sendType; }
        
        public java.util.Date getShippingDate() { return shippingDate; }
        public void setShippingDate(java.util.Date shippingDate) { this.shippingDate = shippingDate; }
        
        public String getShippingAddress() { return shippingAddress; }
        public void setShippingAddress(String shippingAddress) { this.shippingAddress = shippingAddress; }
        
        public String getRemark() { return remark; }
        public void setRemark(String remark) { this.remark = remark; }
        
        public Long getSenderId() { return senderId; }
        public void setSenderId(Long senderId) { this.senderId = senderId; }
        
        public Long getReceiverUserId() { return receiverUserId; }
        public void setReceiverUserId(Long receiverUserId) { this.receiverUserId = receiverUserId; }
        
        public List<ProductDetail> getProductDetails() { return productDetails; }
        public void setProductDetails(List<ProductDetail> productDetails) { this.productDetails = productDetails; }
    }

    /**
     * 产品明细对象
     */
    public static class ProductDetail {
        private Long itemId;
        private String itemCode;
        private String itemName;
        private String specification;
        private Double quantity;
        private String unitName;

        // getter and setter methods
        public Long getItemId() { return itemId; }
        public void setItemId(Long itemId) { this.itemId = itemId; }
        
        public String getItemCode() { return itemCode; }
        public void setItemCode(String itemCode) { this.itemCode = itemCode; }
        
        public String getItemName() { return itemName; }
        public void setItemName(String itemName) { this.itemName = itemName; }
        
        public String getSpecification() { return specification; }
        public void setSpecification(String specification) { this.specification = specification; }
        
        public Double getQuantity() { return quantity; }
        public void setQuantity(Double quantity) { this.quantity = quantity; }
        
        public String getUnitName() { return unitName; }
        public void setUnitName(String unitName) { this.unitName = unitName; }
    }

    /**
     * 更新状态请求对象
     */
    public static class UpdateStatusRequest {
        private Long recordId;
        private String sendStatus;
        private Long receiverUserId;

        // getter and setter methods
        public Long getRecordId() { return recordId; }
        public void setRecordId(Long recordId) { this.recordId = recordId; }
        
        public String getSendStatus() { return sendStatus; }
        public void setSendStatus(String sendStatus) { this.sendStatus = sendStatus; }
        
        public Long getReceiverUserId() { return receiverUserId; }
        public void setReceiverUserId(Long receiverUserId) { this.receiverUserId = receiverUserId; }
    }
    
    /**
     * 查询订单发送记录列表（包含产品明细）
     */
    @PreAuthorize("@ss.hasPermi('mes:sales:order:send:list')")
    @GetMapping("/listWithDetails")
    public TableDataInfo listWithDetails(SalesOrderSendRecord salesOrderSendRecord) {
        startPage();
        List<SalesOrderSendRecordVO> list = salesOrderSendRecordService.selectSalesOrderSendRecordListWithDetails(salesOrderSendRecord);
        return getDataTable(list);
    }
}
-------------
// SalesOrderSendRecord.java
package com.ktg.mes.sales.domain;

import com.fasterxml.jackson.annotation.JsonFormat;
import org.apache.commons.lang3.builder.ToStringBuilder;
import com.ktg.common.core.domain.BaseEntity;
import org.apache.commons.lang3.builder.ToStringStyle;

import java.util.Date;

/**
 * 订单发送记录对象 sales_order_send_record
 */
public class SalesOrderSendRecord extends BaseEntity {
    private static final long serialVersionUID = 1L;

    /** 记录ID */
    private Long recordId;

    /** 通知单编号 */
    private String noticeCode;

    /** 通知单名称 */
    private String noticeName;

    /** 订单ID */
    private Long orderId;

    /** 销售订单编号 */
    private String orderCode;

    /** 合同ID */
    private Long contractId;

    /** 合同编号 */
    private String contractCode;

    /** 客户ID */
    private Long customerId;

    /** 客户简称 */
    private String customerNick;

    /** 产品信息 */
    private String productInfo;

    /** 防腐方案 */
    private String antiCorrosionPlan;

    /** 防腐说明 */
    private String antiCorrosionNote;

    /** 产品特殊情况 */
    private String productSpecialNote;

    /** 包装特殊情况 */
    private String packageSpecialNote;

    /** 其他特殊情况 */
    private String otherSpecialNote;

    /** 发货日期 */
    @JsonFormat(pattern = "yyyy-MM-dd")
    private Date shippingDate;

    /** 收货信息 */
    private String shippingAddress;

    /** 发送类型：WAREHOUSE-仓库, PRODUCTION-生产 */
    private String sendType;

    /** 发送状态：SENT-已发送, RECEIVED-已接收, COMPLETED-已完成 */
    private String sendStatus;

    /** 发送人ID */
    private Long senderId;

    /** 接收人ID */
    private Long receiverUserId;

    /** 发送时间 */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date sendTime;

    /** 接收时间 */
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date receiveTime;

    public void setRecordId(Long recordId) {
        this.recordId = recordId;
    }

    public Long getRecordId() {
        return recordId;
    }

    public void setNoticeCode(String noticeCode) {
        this.noticeCode = noticeCode;
    }

    public String getNoticeCode() {
        return noticeCode;
    }

    public void setNoticeName(String noticeName) {
        this.noticeName = noticeName;
    }

    public String getNoticeName() {
        return noticeName;
    }

    public void setOrderId(Long orderId) {
        this.orderId = orderId;
    }

    public Long getOrderId() {
        return orderId;
    }

    public void setOrderCode(String orderCode) {
        this.orderCode = orderCode;
    }

    public String getOrderCode() {
        return orderCode;
    }

    public void setContractId(Long contractId) {
        this.contractId = contractId;
    }

    public Long getContractId() {
        return contractId;
    }

    public void setContractCode(String contractCode) {
        this.contractCode = contractCode;
    }

    public String getContractCode() {
        return contractCode;
    }

    public void setCustomerId(Long customerId) {
        this.customerId = customerId;
    }

    public Long getCustomerId() {
        return customerId;
    }

    public void setCustomerNick(String customerNick) {
        this.customerNick = customerNick;
    }

    public String getCustomerNick() {
        return customerNick;
    }

    public void setProductInfo(String productInfo) {
        this.productInfo = productInfo;
    }

    public String getProductInfo() {
        return productInfo;
    }

    public void setAntiCorrosionPlan(String antiCorrosionPlan) {
        this.antiCorrosionPlan = antiCorrosionPlan;
    }

    public String getAntiCorrosionPlan() {
        return antiCorrosionPlan;
    }

    public void setAntiCorrosionNote(String antiCorrosionNote) {
        this.antiCorrosionNote = antiCorrosionNote;
    }

    public String getAntiCorrosionNote() {
        return antiCorrosionNote;
    }

    public void setProductSpecialNote(String productSpecialNote) {
        this.productSpecialNote = productSpecialNote;
    }

    public String getProductSpecialNote() {
        return productSpecialNote;
    }

    public void setPackageSpecialNote(String packageSpecialNote) {
        this.packageSpecialNote = packageSpecialNote;
    }

    public String getPackageSpecialNote() {
        return packageSpecialNote;
    }

    public void setOtherSpecialNote(String otherSpecialNote) {
        this.otherSpecialNote = otherSpecialNote;
    }

    public String getOtherSpecialNote() {
        return otherSpecialNote;
    }

    public void setShippingDate(Date shippingDate) {
        this.shippingDate = shippingDate;
    }

    public Date getShippingDate() {
        return shippingDate;
    }

    public void setShippingAddress(String shippingAddress) {
        this.shippingAddress = shippingAddress;
    }

    public String getShippingAddress() {
        return shippingAddress;
    }

    public void setSendType(String sendType) {
        this.sendType = sendType;
    }

    public String getSendType() {
        return sendType;
    }

    public void setSendStatus(String sendStatus) {
        this.sendStatus = sendStatus;
    }

    public String getSendStatus() {
        return sendStatus;
    }

    public void setSenderId(Long senderId) {
        this.senderId = senderId;
    }

    public Long getSenderId() {
        return senderId;
    }

    public void setReceiverUserId(Long receiverUserId) {
        this.receiverUserId = receiverUserId;
    }

    public Long getReceiverUserId() {
        return receiverUserId;
    }

    public void setSendTime(Date sendTime) {
        this.sendTime = sendTime;
    }

    public Date getSendTime() {
        return sendTime;
    }

    public void setReceiveTime(Date receiveTime) {
        this.receiveTime = receiveTime;
    }

    public Date getReceiveTime() {
        return receiveTime;
    }

    @Override
    public String toString() {
        return new ToStringBuilder(this, ToStringStyle.MULTI_LINE_STYLE)
            .append("recordId", getRecordId())
            .append("noticeCode", getNoticeCode())
            .append("noticeName", getNoticeName())
            .append("orderId", getOrderId())
            .append("orderCode", getOrderCode())
            .append("contractId", getContractId())
            .append("contractCode", getContractCode())
            .append("customerId", getCustomerId())
            .append("customerNick", getCustomerNick())
            .append("productInfo", getProductInfo())
            .append("antiCorrosionPlan", getAntiCorrosionPlan())
            .append("antiCorrosionNote", getAntiCorrosionNote())
            .append("productSpecialNote", getProductSpecialNote())
            .append("packageSpecialNote", getPackageSpecialNote())
            .append("otherSpecialNote", getOtherSpecialNote())
            .append("shippingDate", getShippingDate())
            .append("shippingAddress", getShippingAddress())
            .append("sendType", getSendType())
            .append("sendStatus", getSendStatus())
            .append("senderId", getSenderId())
            .append("receiverUserId", getReceiverUserId())
            .append("sendTime", getSendTime())
            .append("receiveTime", getReceiveTime())
            .append("createBy", getCreateBy())
            .append("createTime", getCreateTime())
            .append("updateBy", getUpdateBy())
            .append("updateTime", getUpdateTime())
            .toString();
    }
}
=================
发货通知单页的后端：
package com.ktg.mes.wm.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import com.ktg.common.core.controller.BaseController;
import com.ktg.common.core.domain.AjaxResult;
import com.ktg.common.core.page.TableDataInfo;
import com.ktg.mes.sales.domain.SalesOrderSendRecord;
import com.ktg.mes.sales.service.ISalesOrderSendRecordService;

import java.util.Date;
import java.util.List;

@RestController
@RequestMapping("/mes/wm/salesnotice")
public class WmSalesNoticeController extends BaseController {
    
    @Autowired
    private ISalesOrderSendRecordService salesOrderSendRecordService;
    
    // 查询发货通知记录列表（仓库端）
    @GetMapping("/sendRecordList")
    public TableDataInfo list(SalesOrderSendRecord sendRecord) {
        // 设置查询条件，只查询发送给仓库的记录
        sendRecord.setSendType("WAREHOUSE");
        startPage();
        List<SalesOrderSendRecord> list = salesOrderSendRecordService.selectSalesOrderSendRecordList(sendRecord);
        return getDataTable(list);
    }
    
    // 获取发货通知记录详情
    @GetMapping("/sendRecord/{recordId}")
    public AjaxResult getInfo(@PathVariable("recordId") Long recordId) {
        return AjaxResult.success(salesOrderSendRecordService.selectSalesOrderSendRecordByRecordId(recordId));
    }
    
    // 确认接收发货通知
    @PostMapping("/confirmReceive/{recordId}")
    public AjaxResult confirmReceive(@PathVariable("recordId") Long recordId) {
        SalesOrderSendRecord record = new SalesOrderSendRecord();
        record.setRecordId(recordId);
        record.setSendStatus("RECEIVED");
        record.setReceiveTime(new Date());
        return AjaxResult.success(salesOrderSendRecordService.updateSalesOrderSendRecord(record));
    }
}
------------
